unit tests
This commit is contained in:
@@ -536,82 +536,6 @@ describe('Prometheus Language Provider', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling and metadata', () => {
|
||||
it('should handle disabled metadata lookups gracefully', async () => {
|
||||
const datasource: PrometheusDatasource = {
|
||||
...defaultDatasource,
|
||||
metadataRequest: jest.fn(() => ({ data: { data: ['foo', 'bar'] as string[] } })),
|
||||
lookupsDisabled: false,
|
||||
} as unknown as PrometheusDatasource;
|
||||
const mockedMetadataRequest = jest.mocked(datasource.metadataRequest);
|
||||
const instance = new PrometheusLanguageProvider(datasource);
|
||||
|
||||
expect(mockedMetadataRequest.mock.calls.length).toBe(0);
|
||||
await instance.start();
|
||||
expect(mockedMetadataRequest.mock.calls.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should handle metadata request failures gracefully', async () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
const datasource: PrometheusDatasource = {
|
||||
...defaultDatasource,
|
||||
metadataRequest: jest.fn(() => Promise.reject('rejected')),
|
||||
lookupsDisabled: false,
|
||||
} as unknown as PrometheusDatasource;
|
||||
const mockedMetadataRequest = jest.mocked(datasource.metadataRequest);
|
||||
const instance = new PrometheusLanguageProvider(datasource);
|
||||
|
||||
expect(mockedMetadataRequest.mock.calls.length).toBe(0);
|
||||
const result = await instance.start();
|
||||
expect(result[0]).toBeUndefined();
|
||||
expect(result[1]).toEqual([]);
|
||||
expect(mockedMetadataRequest.mock.calls.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should include cache headers for requests when cacheLevel is set', () => {
|
||||
const timeSnapMinutes = getCacheDurationInMinutes(PrometheusCacheLevel.Medium);
|
||||
const languageProvider = new PrometheusLanguageProvider({
|
||||
...defaultDatasource,
|
||||
cacheLevel: PrometheusCacheLevel.Medium,
|
||||
} as PrometheusDatasource);
|
||||
const fetchLabelValues = languageProvider.fetchLabelValues;
|
||||
const requestSpy = jest.spyOn(languageProvider, 'request');
|
||||
|
||||
fetchLabelValues(getMockTimeRange(), 'job');
|
||||
|
||||
expect(requestSpy).toHaveBeenCalled();
|
||||
expect(requestSpy.mock.calls[0][2]).toEqual({
|
||||
headers: { 'X-Grafana-Cache': `private, max-age=${timeSnapMinutes * 60}` },
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle request errors gracefully', async () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
const languageProvider = new PrometheusLanguageProvider(defaultDatasource);
|
||||
const datasourceRequestMock = jest
|
||||
.spyOn(defaultDatasource, 'metadataRequest')
|
||||
.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
const result = await languageProvider.request('/api/v1/labels', {});
|
||||
|
||||
expect(datasourceRequestMock).toHaveBeenCalled();
|
||||
expect(result).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('should ignore cancelled request errors', async () => {
|
||||
jest.spyOn(console, 'error').mockImplementation();
|
||||
const languageProvider = new PrometheusLanguageProvider(defaultDatasource);
|
||||
const error = { cancelled: true };
|
||||
const datasourceRequestMock = jest.spyOn(defaultDatasource, 'metadataRequest').mockRejectedValue(error);
|
||||
|
||||
const result = await languageProvider.request('/api/v1/labels', {});
|
||||
|
||||
expect(datasourceRequestMock).toHaveBeenCalled();
|
||||
expect(result).toEqual(undefined);
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchSuggestions', () => {
|
||||
it('should send POST request with correct parameters', async () => {
|
||||
const timeRange = getMockTimeRange();
|
||||
@@ -694,171 +618,6 @@ describe('Prometheus Language Provider', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('New PrometheusLanguageProvider Interface', () => {
|
||||
let provider: PrometheusLanguageProviderInterface;
|
||||
|
||||
beforeEach(() => {
|
||||
provider = new PrometheusLanguageProvider(defaultDatasource);
|
||||
});
|
||||
|
||||
describe('Retrieve Methods (Synchronous)', () => {
|
||||
beforeEach(async () => {
|
||||
// Setup the data through public methods
|
||||
// This is the raw API response shape
|
||||
const rawMetadata = {
|
||||
metric1: [
|
||||
{
|
||||
type: 'counter',
|
||||
help: 'help text',
|
||||
},
|
||||
],
|
||||
};
|
||||
// Mock the request to return raw API shape
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(rawMetadata);
|
||||
await provider.queryMetricsMetadata();
|
||||
|
||||
// Setup metrics through start method
|
||||
const metrics = ['metric1', 'metric2'];
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(metrics);
|
||||
await provider.start();
|
||||
});
|
||||
|
||||
it('should retrieve metrics metadata', () => {
|
||||
const result = provider.retrieveMetricsMetadata();
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
metric1: {
|
||||
type: 'counter',
|
||||
help: 'help text',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should retrieve histogram metrics', () => {
|
||||
const result = provider.retrieveHistogramMetrics();
|
||||
expect(result).toEqual(expect.any(Array));
|
||||
});
|
||||
|
||||
it('should retrieve metrics', () => {
|
||||
const result = provider.retrieveMetrics();
|
||||
expect(result).toEqual(expect.any(Array));
|
||||
});
|
||||
|
||||
it('should retrieve label keys', () => {
|
||||
const result = provider.retrieveLabelKeys();
|
||||
expect(result).toEqual(expect.any(Array));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Query Methods (Asynchronous)', () => {
|
||||
const timeRange = getMockTimeRange();
|
||||
|
||||
it('should query metrics metadata', async () => {
|
||||
// Raw API response shape
|
||||
const rawMetadata = {
|
||||
metric1: [
|
||||
{
|
||||
type: 'counter',
|
||||
help: 'help text',
|
||||
},
|
||||
],
|
||||
};
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(rawMetadata);
|
||||
|
||||
const result = await provider.queryMetricsMetadata();
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
metric1: {
|
||||
type: 'counter',
|
||||
help: 'help text',
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('queryLabelKeys', () => {
|
||||
it('should use labels API when supported', async () => {
|
||||
const labelKeys = ['label1', 'label2'];
|
||||
const datasourceWithLabelsAPI = {
|
||||
...defaultDatasource,
|
||||
hasLabelsMatchAPISupport: () => true,
|
||||
} as unknown as PrometheusDatasource;
|
||||
provider = new PrometheusLanguageProvider(datasourceWithLabelsAPI);
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(labelKeys);
|
||||
|
||||
const result = await provider.queryLabelKeys(timeRange, '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual(['label1', 'label2']);
|
||||
});
|
||||
|
||||
it('should use series API when labels API not supported', async () => {
|
||||
const series = [{ __name__: 'metric', label1: 'value1', label2: 'value2' }];
|
||||
const datasourceWithoutLabelsAPI = {
|
||||
...defaultDatasource,
|
||||
hasLabelsMatchAPISupport: () => false,
|
||||
} as unknown as PrometheusDatasource;
|
||||
provider = new PrometheusLanguageProvider(datasourceWithoutLabelsAPI);
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(series);
|
||||
|
||||
const result = await provider.queryLabelKeys(timeRange, '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual(['label1', 'label2']);
|
||||
});
|
||||
|
||||
it('should handle empty series response', async () => {
|
||||
jest.spyOn(provider, 'request').mockResolvedValue([]);
|
||||
|
||||
const result = await provider.queryLabelKeys(timeRange, '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLabelValues', () => {
|
||||
it('should use labels API when supported', async () => {
|
||||
const labelValues = ['value1', 'value2'];
|
||||
const datasourceWithLabelsAPI = {
|
||||
...defaultDatasource,
|
||||
hasLabelsMatchAPISupport: () => true,
|
||||
} as unknown as PrometheusDatasource;
|
||||
provider = new PrometheusLanguageProvider(datasourceWithLabelsAPI);
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(labelValues);
|
||||
|
||||
const result = await provider.queryLabelValues(timeRange, 'job', '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual(['value1', 'value2']);
|
||||
});
|
||||
|
||||
it('should use series API when labels API not supported', async () => {
|
||||
const series = [
|
||||
{ __name__: 'metric', job: 'value1' },
|
||||
{ __name__: 'metric', job: 'value2' },
|
||||
];
|
||||
const datasourceWithoutLabelsAPI = {
|
||||
...defaultDatasource,
|
||||
hasLabelsMatchAPISupport: () => false,
|
||||
} as unknown as PrometheusDatasource;
|
||||
provider = new PrometheusLanguageProvider(datasourceWithoutLabelsAPI);
|
||||
jest.spyOn(provider, 'request').mockResolvedValue(series);
|
||||
|
||||
const result = await provider.queryLabelValues(timeRange, 'job', '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual(['value1', 'value2']);
|
||||
});
|
||||
|
||||
it('should handle empty series response', async () => {
|
||||
jest.spyOn(provider, 'request').mockResolvedValue([]);
|
||||
|
||||
const result = await provider.queryLabelValues(timeRange, 'job', '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Query transformation', () => {
|
||||
|
||||
@@ -1,4 +1,213 @@
|
||||
import { processSeries } from './resource_clients';
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
|
||||
import { LabelsApiClient, processSeries, SeriesApiClient } from './resource_clients';
|
||||
import { PrometheusCacheLevel } from './types';
|
||||
|
||||
const mockTimeRange: TimeRange = {
|
||||
from: dateTime(1681300292392),
|
||||
to: dateTime(1681300293392),
|
||||
raw: {
|
||||
from: 'now-1s',
|
||||
to: 'now',
|
||||
},
|
||||
};
|
||||
|
||||
const mockRequest = jest.fn();
|
||||
const mockGetAdjustedInterval = jest.fn().mockReturnValue({
|
||||
start: '1681300260',
|
||||
end: '1681300320',
|
||||
});
|
||||
const mockGetTimeRangeParams = jest.fn().mockReturnValue({
|
||||
start: '1681300260',
|
||||
end: '1681300320',
|
||||
});
|
||||
const mockInterpolateString = jest.fn((str) => str);
|
||||
const defaultCacheHeaders = { headers: { 'X-Grafana-Cache': 'private, max-age=60' } };
|
||||
|
||||
describe('LabelsApiClient', () => {
|
||||
let client: LabelsApiClient;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
client = new LabelsApiClient(
|
||||
mockRequest,
|
||||
PrometheusCacheLevel.Low,
|
||||
mockGetAdjustedInterval,
|
||||
mockGetTimeRangeParams,
|
||||
mockInterpolateString
|
||||
);
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
it('should initialize metrics and label keys', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['metric1', 'metric2']).mockResolvedValueOnce(['label1', 'label2']);
|
||||
|
||||
await client.start(mockTimeRange);
|
||||
|
||||
expect(client.metrics).toEqual(['metric1', 'metric2']);
|
||||
expect(client.labelKeys).toEqual(['label1', 'label2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryMetrics', () => {
|
||||
it('should fetch metrics and process histogram metrics', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['metric1_bucket', 'metric2_sum', 'metric3_count']);
|
||||
|
||||
const result = await client.queryMetrics(mockTimeRange);
|
||||
|
||||
expect(result.metrics).toEqual(['metric1_bucket', 'metric2_sum', 'metric3_count']);
|
||||
expect(result.histogramMetrics).toEqual(['metric1_bucket']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLabelKeys', () => {
|
||||
it('should fetch and sort label keys', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['label2', 'label1', 'label3']);
|
||||
|
||||
const result = await client.queryLabelKeys(mockTimeRange);
|
||||
|
||||
expect(result).toEqual(['label1', 'label2', 'label3']);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
'/api/v1/labels',
|
||||
{
|
||||
limit: '40000',
|
||||
start: expect.any(String),
|
||||
end: expect.any(String),
|
||||
},
|
||||
defaultCacheHeaders
|
||||
);
|
||||
});
|
||||
|
||||
it('should include match parameter when provided', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['label1', 'label2']);
|
||||
|
||||
await client.queryLabelKeys(mockTimeRange, '{job="grafana"}');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
'/api/v1/labels',
|
||||
{
|
||||
'match[]': '{job="grafana"}',
|
||||
limit: '40000',
|
||||
start: expect.any(String),
|
||||
end: expect.any(String),
|
||||
},
|
||||
defaultCacheHeaders
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLabelValues', () => {
|
||||
it('should fetch label values with proper encoding', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['value1', 'value2']);
|
||||
mockInterpolateString.mockImplementationOnce((str) => str);
|
||||
|
||||
const result = await client.queryLabelValues(mockTimeRange, 'job');
|
||||
|
||||
expect(result).toEqual(['value1', 'value2']);
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
'/api/v1/label/job/values',
|
||||
{
|
||||
start: expect.any(String),
|
||||
end: expect.any(String),
|
||||
limit: '40000',
|
||||
},
|
||||
defaultCacheHeaders
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle UTF-8 label names', async () => {
|
||||
mockRequest.mockResolvedValueOnce(['value1', 'value2']);
|
||||
mockInterpolateString.mockImplementationOnce((str) => 'http.status:sum');
|
||||
|
||||
await client.queryLabelValues(mockTimeRange, '"http.status:sum"');
|
||||
|
||||
expect(mockRequest).toHaveBeenCalledWith(
|
||||
'/api/v1/label/U__http_2e_status:sum/values',
|
||||
{
|
||||
start: expect.any(String),
|
||||
end: expect.any(String),
|
||||
limit: '40000',
|
||||
},
|
||||
defaultCacheHeaders
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SeriesApiClient', () => {
|
||||
let client: SeriesApiClient;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
client = new SeriesApiClient(
|
||||
mockRequest,
|
||||
PrometheusCacheLevel.Low,
|
||||
mockGetAdjustedInterval,
|
||||
mockGetTimeRangeParams,
|
||||
mockInterpolateString
|
||||
);
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
it('should initialize metrics and histogram metrics', async () => {
|
||||
mockRequest.mockResolvedValueOnce([{ __name__: 'metric1_bucket' }, { __name__: 'metric2_sum' }]);
|
||||
|
||||
await client.start(mockTimeRange);
|
||||
|
||||
expect(client.metrics).toEqual(['metric1_bucket', 'metric2_sum']);
|
||||
expect(client.histogramMetrics).toEqual(['metric1_bucket']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryMetrics', () => {
|
||||
it('should fetch and process series data', async () => {
|
||||
mockRequest.mockResolvedValueOnce([
|
||||
{ __name__: 'metric1', label1: 'value1' },
|
||||
{ __name__: 'metric2', label2: 'value2' },
|
||||
]);
|
||||
|
||||
const result = await client.queryMetrics(mockTimeRange);
|
||||
|
||||
expect(result.metrics).toEqual(['metric1', 'metric2']);
|
||||
expect(client.labelKeys).toEqual(['label1', 'label2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLabelKeys', () => {
|
||||
it('should throw error if match parameter is not provided', async () => {
|
||||
await expect(client.queryLabelKeys(mockTimeRange)).rejects.toThrow(
|
||||
'Series endpoint always expects at least one matcher'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch and process label keys from series', async () => {
|
||||
mockRequest.mockResolvedValueOnce([{ __name__: 'metric1', label1: 'value1', label2: 'value2' }]);
|
||||
|
||||
const result = await client.queryLabelKeys(mockTimeRange, '{job="grafana"}');
|
||||
|
||||
expect(result).toEqual(['label1', 'label2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('queryLabelValues', () => {
|
||||
it('should throw error if match parameter is not provided', async () => {
|
||||
await expect(client.queryLabelValues(mockTimeRange, 'job')).rejects.toThrow(
|
||||
'Series endpoint always expects at least one matcher'
|
||||
);
|
||||
});
|
||||
|
||||
it('should fetch and process label values from series', async () => {
|
||||
mockRequest.mockResolvedValueOnce([
|
||||
{ __name__: 'metric1', job: 'grafana' },
|
||||
{ __name__: 'metric2', job: 'prometheus' },
|
||||
]);
|
||||
|
||||
const result = await client.queryLabelValues(mockTimeRange, 'job', '{__name__="metric1"}');
|
||||
|
||||
expect(result).toEqual(['grafana', 'prometheus']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processSeries', () => {
|
||||
it('should extract metrics and label keys from series data', () => {
|
||||
|
||||
Reference in New Issue
Block a user