Skip to main content
Glama
runs-client.test.ts23.1 kB
/** * @fileoverview Tests for runs client * This file adds coverage for the previously untested runs-client.ts */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { RunsClient } from '../../client/runs-client.js'; import { asBranchName } from '../../types/branded.js'; import type { RunsClientTestable, MockDeepSourceClient } from '../test-types.js'; import { TestableRunsClient } from '../utils/test-utils.js'; describe('RunsClient', () => { let runsClient: RunsClient; let mockBaseClient: MockDeepSourceClient; beforeEach(() => { runsClient = new RunsClient('test-api-key'); mockBaseClient = runsClient as unknown as MockDeepSourceClient; // Mock the methods we need mockBaseClient.findProjectByKey = vi.fn(); mockBaseClient.executeGraphQL = vi.fn(); mockBaseClient.logger = { info: vi.fn(), error: vi.fn(), debug: vi.fn(), warn: vi.fn(), }; }); describe('listRuns', () => { it('should fetch runs successfully', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { runs: { edges: [ { node: { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: 'abc123def456', branchName: 'main', baseOid: 'def456abc123', status: 'SUCCESS', createdAt: '2023-01-01T00:00:00Z', updatedAt: '2023-01-01T00:05:00Z', finishedAt: '2023-01-01T00:05:00Z', summary: { occurrencesIntroduced: 5, occurrencesResolved: 3, occurrencesSuppressed: 1, }, repository: { name: 'test-repo', id: 'repo-123', }, }, }, { node: { id: 'run-node-2', runUid: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', commitOid: 'xyz789uvw012', branchName: 'feature-branch', baseOid: 'uvw012xyz789', status: 'RUNNING', createdAt: '2023-01-02T00:00:00Z', updatedAt: '2023-01-02T00:02:00Z', finishedAt: '', summary: { occurrencesIntroduced: 0, occurrencesResolved: 0, occurrencesSuppressed: 0, }, repository: { name: 'test-repo', id: 'repo-123', }, }, }, ], pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null, }, }, }, }, }; mockBaseClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockBaseClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 20, after: null, }); mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); const result = await runsClient.listRuns('test-project'); expect(mockBaseClient.findProjectByKey).toHaveBeenCalledWith('test-project'); expect(mockBaseClient.executeGraphQL).toHaveBeenCalledWith( expect.stringContaining('query getRepositoryRuns'), expect.objectContaining({ login: 'test-org', name: 'test-repo', provider: 'github', }) ); expect(result.items).toHaveLength(2); expect(result.items[0]?.runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); expect(result.items[0]?.status).toBe('SUCCESS'); expect(result.items[0].branchName).toBe('main'); expect(result.items[1].status).toBe('RUNNING'); expect(result.items[1].branchName).toBe('feature-branch'); }); it('should return empty response when project not found', async () => { mockBaseClient.findProjectByKey = vi.fn().mockResolvedValue(null); mockBaseClient.createEmptyPaginatedResponse = vi.fn().mockReturnValue({ items: [], pageInfo: { hasNextPage: false, hasPreviousPage: false }, totalCount: 0, }); const result = await runsClient.listRuns('nonexistent-project'); expect(result.items).toHaveLength(0); expect(result.totalCount).toBe(0); }); it('should handle GraphQL errors', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; mockBaseClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockBaseClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 20, }); mockBaseClient.executeGraphQL = vi.fn().mockRejectedValue(new Error('GraphQL error')); await expect(runsClient.listRuns('test-project')).rejects.toThrow('GraphQL error'); }); it('should handle missing data in response', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: null, }; mockBaseClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockBaseClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 20, }); mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); await expect(runsClient.listRuns('test-project')).rejects.toThrow( 'No data received from GraphQL API' ); }); it('should handle filter parameters', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { runs: { edges: [], pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null, }, }, }, }, }; mockBaseClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockBaseClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 10, after: 'cursor123', }); mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); await runsClient.listRuns('test-project', { first: 10, after: 'cursor123', }); // The normalizePaginationParams is now a static method and its behavior // is tested separately in base-client tests }); }); describe('getRun', () => { it('should fetch run by UUID successfully', async () => { const mockUuid = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; const mockResponse = { data: { run: { id: 'run-node-1', runUid: mockUuid, commitOid: 'abc123def456', branchName: 'main', baseOid: 'def456abc123', status: 'SUCCESS', createdAt: '2023-01-01T00:00:00Z', updatedAt: '2023-01-01T00:05:00Z', finishedAt: '2023-01-01T00:05:00Z', summary: { occurrencesIntroduced: 5, occurrencesResolved: 3, occurrencesSuppressed: 1, }, repository: { name: 'test-repo', id: 'repo-123', }, }, }, }; mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); const result = await runsClient.getRun(mockUuid); expect(mockBaseClient.executeGraphQL).toHaveBeenCalledWith( expect.stringContaining('query getRunByUid'), { runUid: mockUuid } ); expect(result).not.toBeNull(); if (result) { expect(result.runUid).toBe(mockUuid); expect(result.status).toBe('SUCCESS'); expect(result.branchName).toBe('main'); } }); it('should fetch run by commit OID successfully', async () => { const mockCommitOid = 'abc123def456789012345678901234567890abcd'; const mockResponse = { data: { runByCommit: { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: mockCommitOid, branchName: 'main', baseOid: 'def456abc123', status: 'SUCCESS', createdAt: '2023-01-01T00:00:00Z', updatedAt: '2023-01-01T00:05:00Z', finishedAt: '2023-01-01T00:05:00Z', summary: { occurrencesIntroduced: 5, occurrencesResolved: 3, occurrencesSuppressed: 1, }, repository: { name: 'test-repo', id: 'repo-123', }, }, }, }; mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); const result = await runsClient.getRun(mockCommitOid); expect(mockBaseClient.executeGraphQL).toHaveBeenCalledWith( expect.stringContaining('query getRunByCommit'), { commitOid: mockCommitOid } ); expect(result).not.toBeNull(); if (result) { expect(result.commitOid).toBe(mockCommitOid); expect(result.status).toBe('SUCCESS'); } }); it('should return null when run not found', async () => { const mockResponse = { data: null, }; mockBaseClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse); const result = await runsClient.getRun('nonexistent-run-id'); expect(result).toBeNull(); }); it('should return null for NoneType errors', async () => { mockBaseClient.executeGraphQL = vi.fn().mockRejectedValue(new Error('NoneType error')); const result = await runsClient.getRun('nonexistent-run-id'); expect(result).toBeNull(); }); it('should return null for not found errors', async () => { mockBaseClient.executeGraphQL = vi.fn().mockRejectedValue(new Error('Run not found')); const result = await runsClient.getRun('nonexistent-run-id'); expect(result).toBeNull(); }); it('should re-throw other errors', async () => { mockBaseClient.executeGraphQL = vi.fn().mockRejectedValue(new Error('Server error')); await expect(runsClient.getRun('some-run-id')).rejects.toThrow('Server error'); }); }); describe('findMostRecentRunForBranch', () => { it('should find the most recent run for a branch', async () => { const mockRuns = { items: [ { id: 'run-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', branchName: 'main', createdAt: '2023-01-02T00:00:00Z', status: 'SUCCESS', }, { id: 'run-2', runUid: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', branchName: 'main', createdAt: '2023-01-01T00:00:00Z', status: 'SUCCESS', }, { id: 'run-3', runUid: 'b2c3d4e5-f6g7-8901-bcde-f23456789012', branchName: 'feature', createdAt: '2023-01-03T00:00:00Z', status: 'SUCCESS', }, ], pageInfo: { hasNextPage: false, hasPreviousPage: false }, totalCount: 3, }; // Mock the listRuns method runsClient.listRuns = vi.fn().mockResolvedValue(mockRuns); const result = await runsClient.findMostRecentRunForBranch('test-project', 'main'); expect(result).not.toBeNull(); if (result) { expect(result.runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); expect(result.branchName).toBe('main'); expect(result.createdAt).toBe('2023-01-02T00:00:00Z'); } }); it('should throw error when no runs found for branch', async () => { const mockRuns = { items: [ { id: 'run-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', branchName: 'feature', createdAt: '2023-01-01T00:00:00Z', status: 'SUCCESS', }, ], pageInfo: { hasNextPage: false, hasPreviousPage: false }, totalCount: 1, }; runsClient.listRuns = vi.fn().mockResolvedValue(mockRuns); await expect(runsClient.findMostRecentRunForBranch('test-project', 'main')).rejects.toThrow( "No runs found for branch 'main' in project 'test-project'" ); }); it('should handle pagination when searching for runs', async () => { const mockRunsPage1 = { items: [ { id: 'run-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', branchName: 'feature', createdAt: '2023-01-01T00:00:00Z', status: 'SUCCESS', }, ], pageInfo: { hasNextPage: true, hasPreviousPage: false, endCursor: 'cursor1' }, totalCount: 2, }; const mockRunsPage2 = { items: [ { id: 'run-2', runUid: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', branchName: 'main', createdAt: '2023-01-02T00:00:00Z', status: 'SUCCESS', }, ], pageInfo: { hasNextPage: false, hasPreviousPage: true, endCursor: null }, totalCount: 2, }; runsClient.listRuns = vi .fn() .mockResolvedValueOnce(mockRunsPage1) .mockResolvedValueOnce(mockRunsPage2); const result = await runsClient.findMostRecentRunForBranch('test-project', 'main'); expect(runsClient.listRuns).toHaveBeenCalledTimes(2); expect(runsClient.listRuns).toHaveBeenNthCalledWith(1, 'test-project', { first: 50, after: undefined, }); expect(runsClient.listRuns).toHaveBeenNthCalledWith(2, 'test-project', { first: 50, after: 'cursor1', }); expect(result).not.toBeNull(); if (result) { expect(result.runUid).toBe('a1b2c3d4-e5f6-7890-abcd-ef1234567890'); } }); }); describe('getRecentRunIssues', () => { it('should return run with empty issues list', async () => { const mockRun = { id: 'run-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', branchName: 'main', createdAt: '2023-01-01T00:00:00Z', status: 'SUCCESS', }; runsClient.findMostRecentRunForBranch = vi.fn().mockResolvedValue(mockRun); mockBaseClient.executeGraphQL.mockResolvedValue({ data: { run: { occurrences: { edges: [], pageInfo: { hasNextPage: false, hasPreviousPage: false }, }, }, }, }); const result = await runsClient.getRecentRunIssues('test-project', asBranchName('main')); expect(result.run).toEqual(mockRun); expect(result.items).toHaveLength(0); expect(result.totalCount).toBe(0); }); it('should return empty response when no run found', async () => { runsClient.findMostRecentRunForBranch = vi.fn().mockResolvedValue(null); const result = await runsClient.getRecentRunIssues('test-project', asBranchName('main')); expect(result.run).toBeNull(); expect(result.items).toHaveLength(0); expect(result.totalCount).toBe(0); }); it('should handle errors from findMostRecentRunForBranch', async () => { runsClient.findMostRecentRunForBranch = vi .fn() .mockRejectedValue(new Error('Branch not found')); await expect( runsClient.getRecentRunIssues('test-project', asBranchName('main')) ).rejects.toThrow('Branch not found'); }); }); describe('buildRunsQuery', () => { it('should build correct GraphQL query', () => { const query = TestableRunsClient.testBuildRunsQuery(); expect(query).toContain('query getRepositoryRuns'); expect(query).toContain('$login: String!'); expect(query).toContain('$name: String!'); expect(query).toContain('$provider: VCSProvider!'); expect(query).toContain('runs('); expect(query).toContain('summary {'); }); }); describe('buildRunByUidQuery', () => { it('should build correct GraphQL query', () => { const query = TestableRunsClient.testBuildRunByUidQuery(); expect(query).toContain('query getRunByUid'); expect(query).toContain('$runUid: UUID!'); expect(query).toContain('run(runUid: $runUid)'); }); }); describe('buildRunByCommitQuery', () => { it('should build correct GraphQL query', () => { const query = TestableRunsClient.testBuildRunByCommitQuery(); expect(query).toContain('query getRunByCommit'); expect(query).toContain('$commitOid: String!'); expect(query).toContain('runByCommit(commitOid: $commitOid)'); }); }); describe('extractRunsFromResponse', () => { it('should extract runs from GraphQL response', () => { const mockResponseData = { repository: { runs: { edges: [ { node: { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: 'abc123def456', branchName: 'main', baseOid: 'def456abc123', status: 'SUCCESS', createdAt: '2023-01-01T00:00:00Z', updatedAt: '2023-01-01T00:05:00Z', finishedAt: '2023-01-01T00:05:00Z', summary: { occurrencesIntroduced: 5, occurrencesResolved: 3, occurrencesSuppressed: 1, }, repository: { name: 'test-repo', id: 'repo-123', }, }, }, ], }, }, }; const runs = (runsClient as unknown as RunsClientTestable).extractRunsFromResponse( mockResponseData ); expect(runs).toHaveLength(1); expect(runs[0].runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); expect(runs[0].status).toBe('SUCCESS'); expect(runs[0].summary.occurrencesIntroduced).toBe(5); }); it('should handle missing runs in response', () => { const mockResponseData = { repository: {}, }; const runs = (runsClient as unknown as RunsClientTestable).extractRunsFromResponse( mockResponseData ); expect(runs).toHaveLength(0); }); }); describe('extractSingleRunFromResponse', () => { it('should extract run from response with run field', () => { const mockResponseData = { run: { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: 'abc123def456', branchName: 'main', status: 'SUCCESS', summary: {}, repository: {}, }, }; const run = (runsClient as unknown as RunsClientTestable).extractSingleRunFromResponse( mockResponseData ); expect(run).not.toBeNull(); expect(run.runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); }); it('should extract run from response with runByCommit field', () => { const mockResponseData = { runByCommit: { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: 'abc123def456', branchName: 'main', status: 'SUCCESS', summary: {}, repository: {}, }, }; const run = (runsClient as unknown as RunsClientTestable).extractSingleRunFromResponse( mockResponseData ); expect(run).not.toBeNull(); expect(run.runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); }); it('should return null when no run data available', () => { const mockResponseData = {}; const run = (runsClient as unknown as RunsClientTestable).extractSingleRunFromResponse( mockResponseData ); expect(run).toBeNull(); }); }); describe('mapRunNode', () => { it('should map run node to DeepSourceRun', () => { const mockNode = { id: 'run-node-1', runUid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', commitOid: 'abc123def456', branchName: 'main', baseOid: 'def456abc123', status: 'SUCCESS', createdAt: '2023-01-01T00:00:00Z', updatedAt: '2023-01-01T00:05:00Z', finishedAt: '2023-01-01T00:05:00Z', summary: { occurrencesIntroduced: 5, occurrencesResolved: 3, occurrencesSuppressed: 1, }, repository: { name: 'test-repo', id: 'repo-123', }, }; const run = TestableRunsClient.testMapRunNode(mockNode); expect(run.id).toBe('run-node-1'); expect(run.runUid).toBe('f47ac10b-58cc-4372-a567-0e02b2c3d479'); expect(run.commitOid).toBe('abc123def456'); expect(run.branchName).toBe('main'); expect(run.status).toBe('SUCCESS'); expect(run.summary.occurrencesIntroduced).toBe(5); expect(run.repository.name).toBe('test-repo'); }); it('should handle missing fields in node', () => { const mockNode = { id: null, runUid: null, commitOid: null, branchName: null, status: null, summary: null, repository: null, }; const run = TestableRunsClient.testMapRunNode(mockNode); expect(run.id).toBe(''); expect(run.runUid).toBe(''); expect(run.commitOid).toBe(''); expect(run.branchName).toBe(''); expect(run.status).toBe('UNKNOWN'); expect(run.summary.occurrencesIntroduced).toBe(0); expect(run.repository.name).toBe(''); }); }); describe('handleRunsError', () => { it('should return empty response for NoneType errors', () => { const error = new Error('NoneType error'); const result = (runsClient as unknown as RunsClientTestable).handleRunsError(error); expect(result.items).toHaveLength(0); expect(result.totalCount).toBe(0); expect(result.pageInfo.hasNextPage).toBe(false); }); it('should re-throw non-NoneType errors', () => { const error = new Error('Other error'); expect(() => { (runsClient as unknown as RunsClientTestable).handleRunsError(error); }).toThrow('Other error'); }); }); });

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