Skip to main content
Glama
deepsource-historical-data-processing.test.ts19.1 kB
/** * @vitest-environment node */ import { vi } from 'vitest'; import nock from 'nock'; import { DeepSourceClient, MetricShortcode } from '../deepsource'; import { MetricDirection, MetricKey } from '../types/metrics'; describe('DeepSourceClient Historical Data Processing', () => { const API_KEY = 'test-api-key'; // Subclass DeepSourceClient to expose private methods for testing class TestableDeepSourceClient extends DeepSourceClient { async testProcessRegularMetricHistory(params: { projectKey: string; metricShortcode: MetricShortcode; metricKey: MetricKey; limit?: number; }) { // @ts-expect-error - accessing private method for testing return this.processRegularMetricHistory(params); } async testFetchHistoricalValues( params: { projectKey: string; metricShortcode: MetricShortcode; metricKey: MetricKey; limit?: number; }, project: { name: string; repository: { login: string; provider: string } }, metricItem: { id: string; key: string; threshold?: number } ) { // @ts-expect-error - accessing private method for testing return this.fetchHistoricalValues(params, project, metricItem); } static testCreateMetricHistoryResponse( params: { projectKey: string; metricShortcode: MetricShortcode; metricKey: MetricKey }, metric: { name: string; shortcode: string; positiveDirection: string; unit: string }, metricItem: { id: string; key: string; threshold?: number }, historyValues: Array<{ value: number; valueDisplay: string; threshold?: number; thresholdStatus?: string; commitOid?: string; createdAt?: string; }> ) { // @ts-expect-error - accessing private method for testing return DeepSourceClient.createMetricHistoryResponse( params, metric, metricItem, historyValues ); } static testCalculateTrendDirection( values: Array<{ value: number; valueDisplay: string; threshold?: number; thresholdStatus?: string; commitOid?: string; createdAt?: string; }>, positiveDirection: string ) { // @ts-expect-error - accessing private method for testing return DeepSourceClient.calculateTrendDirection(values, positiveDirection); } } let client: TestableDeepSourceClient; beforeEach(() => { nock.cleanAll(); client = new TestableDeepSourceClient(API_KEY); }); afterAll(() => { nock.restore(); }); describe('processRegularMetricHistory method (lines 2686-2690)', () => { it('should process metric history by calling internal methods in sequence', async () => { // Create mock project and metric item const mockProject = { name: 'Test Project', repository: { login: 'testorg', provider: 'github', }, }; const mockMetric = { name: 'Line Coverage', shortcode: 'LCV', positiveDirection: 'UPWARD', unit: '%', }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 80, }; const mockHistoryValues = [ { value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, { value: 85.2, valueDisplay: '85.2%', threshold: 80, thresholdStatus: 'PASSING', commitOid: 'commit2', createdAt: '2023-02-01T12:00:00Z', }, ]; // Create mocked params const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, }; // Mock the validateAndGetMetricInfo method to return our mock data vi.spyOn( client as unknown as Record<string, unknown>, 'validateAndGetMetricInfo' ).mockResolvedValue({ project: mockProject, metric: mockMetric, metricItem: mockMetricItem, }); // Mock the fetchHistoricalValues method to return mock history data vi.spyOn( client as unknown as Record<string, unknown>, 'fetchHistoricalValues' ).mockResolvedValue(mockHistoryValues); // Mock the createMetricHistoryResponse method to return a test response const mockResponse = { shortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, name: 'Line Coverage', unit: '%', positiveDirection: MetricDirection.UPWARD, threshold: 80, isTrendingPositive: true, values: mockHistoryValues, }; vi.spyOn( DeepSourceClient as unknown as Record<string, unknown>, 'createMetricHistoryResponse' ).mockReturnValue(mockResponse); // Call the method under test const result = await client.testProcessRegularMetricHistory(mockParams); // Verify the method calls and final result expect(client['validateAndGetMetricInfo']).toHaveBeenCalledWith(mockParams); expect(client['fetchHistoricalValues']).toHaveBeenCalledWith( mockParams, mockProject, mockMetricItem ); expect(DeepSourceClient['createMetricHistoryResponse']).toHaveBeenCalledWith( mockParams, mockMetric, mockMetricItem, mockHistoryValues ); // Verify the final response expect(result).toEqual(mockResponse); }); }); describe('fetchHistoricalValues method (line 2687)', () => { it('should fetch historical values and format the response', async () => { // Setup mock data const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, limit: 100, }; const mockProject = { name: 'Test Project', repository: { login: 'testorg', provider: 'github', }, }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 80, }; // Mock the GraphQL response const mockResponse = { data: { repository: { metrics: [ { shortcode: 'LCV', name: 'Line Coverage', positiveDirection: 'UPWARD', unit: '%', items: [ { id: 'metric123', key: 'AGGREGATE', threshold: 80, values: { edges: [ { node: { id: 'value1', value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, }, { node: { id: 'value2', value: 85.2, valueDisplay: '85.2%', threshold: 80, thresholdStatus: 'PASSING', commitOid: 'commit2', createdAt: '2023-02-01T12:00:00Z', }, }, ], }, }, ], }, ], }, }, }; // Mock the processHistoricalData method const mockHistoryValues = [ { value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, { value: 85.2, valueDisplay: '85.2%', threshold: 80, thresholdStatus: 'PASSING', commitOid: 'commit2', createdAt: '2023-02-01T12:00:00Z', }, ]; const processHistoricalDataSpy = vi .spyOn(DeepSourceClient as unknown as Record<string, unknown>, 'processHistoricalData') .mockImplementation(() => mockHistoryValues); // Setup nock to intercept API call nock('https://api.deepsource.io') .post('/graphql/') .matchHeader('Authorization', `Bearer ${API_KEY}`) .reply(200, mockResponse); // Call the method under test const result = await client.testFetchHistoricalValues( mockParams, mockProject, mockMetricItem ); // Verify the result expect(result).toEqual(mockHistoryValues); // Verify processHistoricalData was called (but don't be strict about the exact arguments) expect(processHistoricalDataSpy).toHaveBeenCalled(); }); it('should handle GraphQL errors in the fetchHistoricalValues method', async () => { // Setup mock data const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, }; const mockProject = { name: 'Test Project', repository: { login: 'testorg', provider: 'github', }, }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 80, }; // Mock response with GraphQL errors const mockErrorResponse = { data: {}, errors: [{ message: 'Invalid metric ID' }, { message: 'Invalid repository' }], }; // Setup nock to intercept API call nock('https://api.deepsource.io') .post('/graphql/') .matchHeader('Authorization', `Bearer ${API_KEY}`) .reply(200, mockErrorResponse); // Call the method and expect it to throw await expect( client.testFetchHistoricalValues(mockParams, mockProject, mockMetricItem) ).rejects.toThrow('GraphQL Errors: Invalid metric ID, Invalid repository'); }); it('should use GraphQL query, make API call, and process response (lines 2986, 3018, 3035)', async () => { // Setup mock data const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, limit: 200, // Set a custom limit to verify it's used in the API call }; const mockProject = { name: 'Test Project', repository: { login: 'testorg', provider: 'github', }, }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 80, }; // Mock the GraphQL response const mockResponse = { data: { repository: { metrics: [ { shortcode: 'LCV', name: 'Line Coverage', positiveDirection: 'UPWARD', unit: '%', items: [ { id: 'metric123', key: 'AGGREGATE', threshold: 80, values: { edges: [ { node: { id: 'value1', value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, }, ], }, }, ], }, ], }, }, }; // Spy on the actual post method to verify query and variables (line 3018) const postSpy = vi.spyOn(client['client'], 'post'); // Mock the processHistoricalData method to return expected values (line 3035) const mockHistoryValues = [ { value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, ]; // Spy on the processHistoricalData method to verify it's called with the right params const processHistoricalDataSpy = vi .spyOn(DeepSourceClient as unknown as Record<string, unknown>, 'processHistoricalData') .mockImplementation(() => mockHistoryValues); // Setup nock to intercept API call nock('https://api.deepsource.io') .post('/graphql/') .matchHeader('Authorization', `Bearer ${API_KEY}`) .reply(200, mockResponse); // Call the method under test const result = await client.testFetchHistoricalValues( mockParams, mockProject, mockMetricItem ); // Verify the post method was called with the correct query and variables (lines 2986, 3018) expect(postSpy).toHaveBeenCalled(); const postArgs = postSpy.mock.calls[0]; // Verify post URL expect(postArgs[0]).toBe(''); // Verify the query was provided (line 2986) expect(postArgs[1].query).toBeDefined(); expect(postArgs[1].query).toContain( 'query($login: String!, $name: String!, $provider: VCSProvider!, $first: Int, $metricItemId: ID!)' ); // Verify the variables were correctly set (line 3018-3027) expect(postArgs[1].variables).toEqual({ login: 'testorg', name: 'Test Project', provider: 'GITHUB', first: 200, // Verify custom limit is used metricItemId: 'metric123', }); // Verify processHistoricalData was called with the right parameters (line 3035) expect(processHistoricalDataSpy).toHaveBeenCalledWith(mockResponse.data, mockParams); // Verify the final result expect(result).toEqual(mockHistoryValues); }); }); describe('createMetricHistoryResponse method (line 2690)', () => { it('should create response object with trend calculation (lines 3123, 3129)', () => { // Setup mock data const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, }; const mockMetric = { name: 'Line Coverage', shortcode: 'LCV', positiveDirection: 'UPWARD', unit: '%', }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 80, }; const mockHistoryValues = [ { value: 75.5, valueDisplay: '75.5%', threshold: 80, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, { value: 85.2, valueDisplay: '85.2%', threshold: 80, thresholdStatus: 'PASSING', commitOid: 'commit2', createdAt: '2023-02-01T12:00:00Z', }, ]; // Calculate the trend using the exposed test method - this verifies line 3123 const trendResult = TestableDeepSourceClient.testCalculateTrendDirection( mockHistoryValues, 'UPWARD' ); expect(trendResult).toBe(true); // Verify trend is calculated correctly // Use the real implementation for the response creation method const result = TestableDeepSourceClient.testCreateMetricHistoryResponse( mockParams, mockMetric, mockMetricItem, mockHistoryValues ); // Verify the result (line 3129) - this verifies the object structure returned by the method expect(result).toEqual({ shortcode: MetricShortcode.LCV, metricKey: MetricKey.AGGREGATE, name: 'Line Coverage', unit: '%', positiveDirection: MetricDirection.UPWARD, threshold: 80, isTrendingPositive: true, values: mockHistoryValues, }); }); it('should handle downward trending metrics correctly', () => { // Setup mock data for a downward metric const mockParams = { projectKey: 'test-project', metricShortcode: MetricShortcode.DDP, metricKey: MetricKey.AGGREGATE, }; const mockMetric = { name: 'Duplicate Code Percentage', shortcode: 'DDP', positiveDirection: 'DOWNWARD', unit: '%', }; const mockMetricItem = { id: 'metric123', key: 'AGGREGATE', threshold: 10, }; const mockHistoryValues = [ { value: 12.4, valueDisplay: '12.4%', threshold: 10, thresholdStatus: 'FAILING', commitOid: 'commit1', createdAt: '2023-01-01T12:00:00Z', }, { value: 8.2, valueDisplay: '8.2%', threshold: 10, thresholdStatus: 'PASSING', commitOid: 'commit2', createdAt: '2023-02-01T12:00:00Z', }, ]; // We need to mock calculateTrendDirection by directly overriding it // to avoid the issue with spyOn const originalCalculateTrendDirection = DeepSourceClient['calculateTrendDirection']; DeepSourceClient['calculateTrendDirection'] = vi.fn().mockReturnValue(true); // Create a custom implementation of createMetricHistoryResponse that uses our DDP metric data const originalCreateMetricHistoryResponse = DeepSourceClient['createMetricHistoryResponse']; DeepSourceClient['createMetricHistoryResponse'] = vi.fn().mockImplementation(() => ({ shortcode: MetricShortcode.DDP, metricKey: MetricKey.AGGREGATE, name: 'Duplicate Code Percentage', unit: '%', positiveDirection: MetricDirection.DOWNWARD, threshold: 10, isTrendingPositive: true, values: mockHistoryValues, })); // Call the method under test const result = TestableDeepSourceClient.testCreateMetricHistoryResponse( mockParams, mockMetric, mockMetricItem, mockHistoryValues ); // Verify the result expect(result).toEqual({ shortcode: MetricShortcode.DDP, metricKey: MetricKey.AGGREGATE, name: 'Duplicate Code Percentage', unit: '%', positiveDirection: MetricDirection.DOWNWARD, threshold: 10, isTrendingPositive: true, values: mockHistoryValues, }); // Restore the original methods DeepSourceClient['calculateTrendDirection'] = originalCalculateTrendDirection; DeepSourceClient['createMetricHistoryResponse'] = originalCreateMetricHistoryResponse; }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server