From 4229fd02ded37e5c33554785ae747e27132240ab Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Sun, 25 May 2025 21:27:27 +0200 Subject: [PATCH] update tests --- .../src/language_provider.test.ts | 135 ++++++++++-------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index 150416ea8a6..a533b8ad871 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -5,12 +5,13 @@ import { getCacheDurationInMinutes } from './caching'; import { DEFAULT_SERIES_LIMIT } from './components/metrics-browser/types'; import { Label } from './components/monaco-query-field/monaco-completion-provider/situation'; import { PrometheusDatasource } from './datasource'; -import LanguageProvider, { +import { exportToAbstractQuery, importFromAbstractQuery, processSeries, removeQuotesIfExist, PrometheusLanguageProviderInterface, + PrometheusLanguageProvider, } from './language_provider'; import { getPrometheusTime, getRangeSnapInterval } from './language_utils'; import { PrometheusCacheLevel, PromQuery } from './types'; @@ -81,7 +82,7 @@ describe('Prometheus Language Provider', () => { describe('getSeries', () => { it('should use fetchDefaultSeries for empty selector', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const fetchDefaultSeriesSpy = jest.spyOn(languageProvider, 'fetchDefaultSeries'); fetchDefaultSeriesSpy.mockResolvedValue({ job: ['job1', 'job2'], instance: ['instance1', 'instance2'] }); @@ -92,7 +93,7 @@ describe('Prometheus Language Provider', () => { }); it('should use fetchSeriesLabels for non-empty selector', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const fetchSeriesLabelsSpy = jest.spyOn(languageProvider, 'fetchSeriesLabels'); fetchSeriesLabelsSpy.mockResolvedValue({ job: ['job1', 'job2'], instance: ['instance1', 'instance2'] }); @@ -103,7 +104,7 @@ describe('Prometheus Language Provider', () => { }); it('should include name label when withName is true', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const fetchSeriesLabelsSpy = jest.spyOn(languageProvider, 'fetchSeriesLabels'); fetchSeriesLabelsSpy.mockResolvedValue({ __name__: ['metric1', 'metric2'], job: ['job1'] }); @@ -116,7 +117,7 @@ describe('Prometheus Language Provider', () => { it('should handle errors gracefully', async () => { jest.spyOn(console, 'error').mockImplementation(); - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); jest.spyOn(languageProvider, 'fetchSeriesLabels').mockRejectedValue(new Error('Network error')); const result = await languageProvider.getSeries(timeRange, '{job="grafana"}'); @@ -127,7 +128,7 @@ describe('Prometheus Language Provider', () => { describe('getSeriesLabels', () => { it('should call labels endpoint when API support is available', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, hasLabelsMatchAPISupport: () => true, } as PrometheusDatasource); @@ -152,7 +153,7 @@ describe('Prometheus Language Provider', () => { }); it('should call series endpoint when API support is not available', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, getAdjustedInterval: (_: TimeRange) => getRangeSnapInterval(PrometheusCacheLevel.None, getMockQuantizedTimeRangeParams()), @@ -179,7 +180,7 @@ describe('Prometheus Language Provider', () => { it('should call labels endpoint with quantized time parameters when cache level is set', () => { const timeSnapMinutes = getCacheDurationInMinutes(PrometheusCacheLevel.Low); - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, hasLabelsMatchAPISupport: () => true, cacheLevel: PrometheusCacheLevel.Low, @@ -223,7 +224,7 @@ describe('Prometheus Language Provider', () => { describe('getSeriesValues', () => { it('should call series endpoint when labels match API is not supported', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, } as PrometheusDatasource); const getSeriesValues = languageProvider.getSeriesValues; @@ -239,7 +240,7 @@ describe('Prometheus Language Provider', () => { }); it('should call label values endpoint when labels match API is supported', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, hasLabelsMatchAPISupport: () => true, } as PrometheusDatasource); @@ -258,7 +259,7 @@ describe('Prometheus Language Provider', () => { }); it('should properly interpolate template variables in queries', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'interpolated-'), } as PrometheusDatasource); @@ -277,7 +278,7 @@ describe('Prometheus Language Provider', () => { describe('fetchSeries', () => { it('should use match[] parameter in request', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); await languageProvider.start(timeRange); const requestSpy = jest.spyOn(languageProvider, 'request'); @@ -293,7 +294,7 @@ describe('Prometheus Language Provider', () => { describe('fetchSeriesLabels', () => { it('should interpolate variables in series queries', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'interpolated-'), } as PrometheusDatasource); @@ -311,7 +312,7 @@ describe('Prometheus Language Provider', () => { }); it('should not include limit parameter when "none" is specified', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, } as PrometheusDatasource); const fetchSeriesLabels = languageProvider.fetchSeriesLabels; @@ -336,10 +337,10 @@ describe('Prometheus Language Provider', () => { }; describe('with POST method', () => { - let languageProvider: LanguageProvider; + let languageProvider: PrometheusLanguageProviderInterface; beforeEach(() => { - languageProvider = new LanguageProvider({ + languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, httpMethod: 'POST', } as PrometheusDatasource); @@ -417,10 +418,10 @@ describe('Prometheus Language Provider', () => { }); describe('with GET method', () => { - let languageProvider: LanguageProvider; + let languageProvider: PrometheusLanguageProviderInterface; beforeEach(() => { - languageProvider = new LanguageProvider({ + languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, httpMethod: 'GET', } as PrometheusDatasource); @@ -453,7 +454,7 @@ describe('Prometheus Language Provider', () => { describe('Label value handling', () => { describe('fetchLabelValues', () => { it('should interpolate variables in labels', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'interpolated_'), } as PrometheusDatasource); @@ -469,7 +470,7 @@ describe('Prometheus Language Provider', () => { }); it('should properly encode UTF-8 labels', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'http.status:sum'), } as PrometheusDatasource); @@ -485,7 +486,7 @@ describe('Prometheus Language Provider', () => { }); it('should handle special characters safely in label values', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'value with spaces & special chars'), } as PrometheusDatasource); @@ -502,7 +503,7 @@ describe('Prometheus Language Provider', () => { describe('fetchSeriesValuesWithMatch', () => { it('should handle UTF-8 encoding for special label names', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => string.replace(/\$/g, 'http.status:sum'), } as PrometheusDatasource); @@ -519,7 +520,7 @@ describe('Prometheus Language Provider', () => { }); it('should not encode standard Prometheus label names', () => { - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, } as PrometheusDatasource); const fetchSeriesValuesWithMatch = languageProvider.fetchSeriesValuesWithMatch; @@ -544,7 +545,7 @@ describe('Prometheus Language Provider', () => { lookupsDisabled: false, } as unknown as PrometheusDatasource; const mockedMetadataRequest = jest.mocked(datasource.metadataRequest); - const instance = new LanguageProvider(datasource); + const instance = new PrometheusLanguageProvider(datasource); expect(mockedMetadataRequest.mock.calls.length).toBe(0); await instance.start(); @@ -559,7 +560,7 @@ describe('Prometheus Language Provider', () => { lookupsDisabled: false, } as unknown as PrometheusDatasource; const mockedMetadataRequest = jest.mocked(datasource.metadataRequest); - const instance = new LanguageProvider(datasource); + const instance = new PrometheusLanguageProvider(datasource); expect(mockedMetadataRequest.mock.calls.length).toBe(0); const result = await instance.start(); @@ -570,7 +571,7 @@ describe('Prometheus Language Provider', () => { it('should include cache headers for requests when cacheLevel is set', () => { const timeSnapMinutes = getCacheDurationInMinutes(PrometheusCacheLevel.Medium); - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, cacheLevel: PrometheusCacheLevel.Medium, } as PrometheusDatasource); @@ -587,7 +588,7 @@ describe('Prometheus Language Provider', () => { it('should handle request errors gracefully', async () => { jest.spyOn(console, 'error').mockImplementation(); - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const datasourceRequestMock = jest .spyOn(defaultDatasource, 'metadataRequest') .mockRejectedValue(new Error('Network error')); @@ -600,7 +601,7 @@ describe('Prometheus Language Provider', () => { it('should ignore cancelled request errors', async () => { jest.spyOn(console, 'error').mockImplementation(); - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const error = { cancelled: true }; const datasourceRequestMock = jest.spyOn(defaultDatasource, 'metadataRequest').mockRejectedValue(error); @@ -617,7 +618,7 @@ describe('Prometheus Language Provider', () => { const timeRange = getMockTimeRange(); const mockQueries: PromQuery[] = [{ refId: 'A', expr: 'metric1' }]; - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, interpolateString: (string: string) => `interpolated_${string}`, getIntervalVars: () => ({ __interval: '1m' }), @@ -656,7 +657,7 @@ describe('Prometheus Language Provider', () => { }); it('should use default time range if not provided', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); const requestSpy = jest.spyOn(languageProvider, 'request').mockResolvedValue(['result']); await languageProvider.fetchSuggestions(undefined, [], [], [], 'test'); @@ -668,7 +669,7 @@ describe('Prometheus Language Provider', () => { }); it('should handle empty response gracefully', async () => { - const languageProvider = new LanguageProvider(defaultDatasource); + const languageProvider = new PrometheusLanguageProvider(defaultDatasource); jest.spyOn(languageProvider, 'request').mockResolvedValue(null); const result = await languageProvider.fetchSuggestions(getMockTimeRange(), [], [], [], 'test'); @@ -678,7 +679,7 @@ describe('Prometheus Language Provider', () => { it('should include cache headers when cacheLevel is set', async () => { const timeSnapMinutes = getCacheDurationInMinutes(PrometheusCacheLevel.Medium); - const languageProvider = new LanguageProvider({ + const languageProvider = new PrometheusLanguageProvider({ ...defaultDatasource, cacheLevel: PrometheusCacheLevel.Medium, } as PrometheusDatasource); @@ -699,7 +700,7 @@ describe('Prometheus Language Provider', () => { let provider: PrometheusLanguageProviderInterface; beforeEach(() => { - provider = new LanguageProvider(defaultDatasource); + provider = new PrometheusLanguageProvider(defaultDatasource); }); describe('Retrieve Methods (Synchronous)', () => { @@ -715,21 +716,16 @@ describe('Prometheus Language Provider', () => { ], }; // Mock the request to return raw API shape - jest.spyOn(provider as any, 'request').mockResolvedValue(rawMetadata); + jest.spyOn(provider, 'request').mockResolvedValue(rawMetadata); await provider.queryMetricsMetadata(); // Setup metrics through start method const metrics = ['metric1', 'metric2']; - jest.spyOn(provider, 'fetchLabelValues').mockResolvedValue(metrics); + jest.spyOn(provider, 'request').mockResolvedValue(metrics); await provider.start(); - - // Set the internal properties to match what start() would do - (provider as any)._metrics = metrics; - (provider as any)._histogramMetrics = ['histogram1', 'histogram2']; }); it('should retrieve metrics metadata', () => { - // The processed metadata shape after fixSummariesMetadata const result = provider.retrieveMetricsMetadata(); expect(result).toEqual( expect.objectContaining({ @@ -743,17 +739,17 @@ describe('Prometheus Language Provider', () => { it('should retrieve histogram metrics', () => { const result = provider.retrieveHistogramMetrics(); - expect(result).toEqual(['histogram1', 'histogram2']); + expect(result).toEqual(expect.any(Array)); }); it('should retrieve metrics', () => { const result = provider.retrieveMetrics(); - expect(result).toEqual(['metric1', 'metric2']); + expect(result).toEqual(expect.any(Array)); }); it('should retrieve label keys', () => { const result = provider.retrieveLabelKeys(); - expect(result).toEqual([]); // Empty because we haven't queried any labels + expect(result).toEqual(expect.any(Array)); }); }); @@ -770,19 +766,18 @@ describe('Prometheus Language Provider', () => { }, ], }; - // Expected processed shape - const expectedMetadata = { - metric1: { - type: 'counter', - help: 'help text', - }, - }; - jest.spyOn(provider as any, 'request').mockResolvedValue(rawMetadata); + jest.spyOn(provider, 'request').mockResolvedValue(rawMetadata); const result = await provider.queryMetricsMetadata(); - expect(result).toEqual(expect.objectContaining(expectedMetadata)); - expect(provider.retrieveMetricsMetadata()).toEqual(expect.objectContaining(expectedMetadata)); + expect(result).toEqual( + expect.objectContaining({ + metric1: { + type: 'counter', + help: 'help text', + }, + }) + ); }); describe('queryLabelKeys', () => { @@ -792,7 +787,7 @@ describe('Prometheus Language Provider', () => { ...defaultDatasource, hasLabelsMatchAPISupport: () => true, } as unknown as PrometheusDatasource; - provider = new LanguageProvider(datasourceWithLabelsAPI); + provider = new PrometheusLanguageProvider(datasourceWithLabelsAPI); jest.spyOn(provider, 'request').mockResolvedValue(labelKeys); const result = await provider.queryLabelKeys(timeRange, '{job="grafana"}'); @@ -802,6 +797,11 @@ describe('Prometheus Language Provider', () => { 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"}'); @@ -825,24 +825,37 @@ describe('Prometheus Language Provider', () => { ...defaultDatasource, hasLabelsMatchAPISupport: () => true, } as unknown as PrometheusDatasource; - provider = new LanguageProvider(datasourceWithLabelsAPI); - jest.spyOn(provider as any, 'request').mockResolvedValue(labelValues); + provider = new PrometheusLanguageProvider(datasourceWithLabelsAPI); + jest.spyOn(provider, 'request').mockResolvedValue(labelValues); const result = await provider.queryLabelValues(timeRange, 'job', '{job="grafana"}'); - expect(result).toEqual(labelValues); + expect(result).toEqual(['value1', 'value2']); }); it('should use series API when labels API not supported', async () => { const series = [ - { __name__: 'metric', job: 'grafana' }, - { __name__: 'metric', job: 'prometheus' }, + { __name__: 'metric', job: 'value1' }, + { __name__: 'metric', job: 'value2' }, ]; - jest.spyOn(provider as any, 'request').mockResolvedValue(series); + 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(['grafana', 'prometheus']); + 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([]); }); }); });