/**
* @vitest-environment node
*/
import { vi, beforeEach, describe, expect, it } from 'vitest';
// Mock the logger module
vi.mock('../utils/logger.js', () => ({
defaultLogger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
})),
}));
// Mock axios module before any imports
vi.mock('axios', () => ({
default: {
create: vi.fn(() => ({
interceptors: {
request: { use: vi.fn() },
response: { use: vi.fn() },
},
post: vi.fn(),
})),
},
}));
// Import axios to get the mocked version
const axios = await import('axios');
const mockAxios = axios.default as any; // skipcq: JS-0323
// Import DeepSourceClient after mocking
const { DeepSourceClient } = await import('../deepsource.js');
describe('DeepSourceClient - getRecentRunIssues', () => {
let client: InstanceType<typeof DeepSourceClient>;
let mockAxiosInstance: Record<string, unknown>;
beforeEach(() => {
vi.clearAllMocks();
// Set up mocked axios instance
mockAxiosInstance = {
interceptors: {
request: { use: vi.fn() },
response: { use: vi.fn() },
},
post: vi.fn(),
};
const mockAxiosCreate = mockAxios.create as ReturnType<typeof vi.fn>;
mockAxiosCreate.mockReturnValue(mockAxiosInstance);
// Create client instance
client = new DeepSourceClient({ apiKey: 'test-key' });
// Mock listProjects to return test project
vi.spyOn(client, 'listProjects').mockResolvedValue([
{
name: 'Test Project',
key: 'test-project',
repository: {
login: 'testuser',
provider: 'GITHUB',
},
defaultBranch: 'main',
isPrivate: false,
isActivated: true,
},
]);
});
describe('getRecentRunIssues', () => {
it('should successfully retrieve issues from the most recent run', async () => {
// Mock the findMostRecentRun method
vi.spyOn(
client as unknown as { findMostRecentRun: ReturnType<typeof vi.fn> },
'findMostRecentRun'
).mockResolvedValue({
id: 'run1',
runUid: 'run-uid-1',
commitOid: 'commit1',
branchName: 'main',
baseOid: 'base1',
status: 'SUCCESS' as const,
createdAt: '2023-01-02T00:00:00Z',
updatedAt: '2023-01-02T00:00:00Z',
finishedAt: '2023-01-02T00:00:00Z',
summary: {
occurrencesIntroduced: 5,
occurrencesResolved: 3,
occurrencesSuppressed: 0,
occurrenceDistributionByAnalyzer: [],
occurrenceDistributionByCategory: [],
},
repository: {
id: 'repo1',
name: 'Test Project',
},
});
// Mock the initial checks fetch response
const checksListResponse = {
data: {
data: {
run: {
checks: {
edges: [
{
node: {
id: 'check1',
analyzer: {
shortcode: 'javascript',
},
},
},
],
pageInfo: {
hasNextPage: false,
endCursor: null,
},
},
},
},
},
};
// Mock the occurrences fetch response for the check
const occurrencesResponse = {
data: {
data: {
node: {
id: 'check1',
occurrences: {
edges: [
{
node: {
id: 'occ1',
issue: {
shortcode: 'JS-0001',
title: 'Test Issue',
category: 'BUG',
severity: 'HIGH',
description: 'Test description',
tags: ['test'],
},
path: 'test.js',
beginLine: 10,
},
},
],
pageInfo: {
hasNextPage: false,
endCursor: null,
},
totalCount: 1,
},
},
},
},
};
// Mock the responses in order
mockAxiosInstance.post.mockResolvedValueOnce(checksListResponse);
mockAxiosInstance.post.mockResolvedValueOnce(occurrencesResponse);
// Execute
const result = await client.getRecentRunIssues('test-project', 'main');
// Verify
expect(result.items).toHaveLength(1);
expect(result.items[0]).toMatchObject({
id: 'occ1',
shortcode: 'JS-0001',
title: 'Test Issue',
category: 'BUG',
severity: 'HIGH',
status: 'OPEN',
issue_text: 'Test description',
file_path: 'test.js',
line_number: 10,
tags: ['test'],
});
expect(result.run.runUid).toBe('run-uid-1');
expect(result.pageInfo.hasNextPage).toBe(false);
expect(result.totalCount).toBe(1);
});
it('should handle project not found error', async () => {
// Mock listProjects to return empty array
vi.spyOn(client, 'listProjects').mockResolvedValue([]);
// Execute and expect error
await expect(client.getRecentRunIssues('non-existent', 'main')).rejects.toThrow(
'Project with key non-existent not found'
);
});
it('should handle no runs found for branch', async () => {
// Mock findMostRecentRun to throw the expected error
vi.spyOn(
client as unknown as { findMostRecentRun: ReturnType<typeof vi.fn> },
'findMostRecentRun'
).mockRejectedValue(
new Error("No runs found for branch 'non-existent-branch' in project 'test-project'")
);
// Execute and expect error
await expect(
client.getRecentRunIssues('test-project', 'non-existent-branch')
).rejects.toThrow("No runs found for branch 'non-existent-branch' in project 'test-project'");
});
it('should handle GraphQL errors in checks fetch', async () => {
// Mock findMostRecentRun to succeed
vi.spyOn(
client as unknown as { findMostRecentRun: ReturnType<typeof vi.fn> },
'findMostRecentRun'
).mockResolvedValue({
id: 'run1',
runUid: 'run-uid-1',
commitOid: 'commit1',
branchName: 'main',
baseOid: 'base1',
status: 'SUCCESS' as const,
createdAt: '2023-01-02T00:00:00Z',
updatedAt: '2023-01-02T00:00:00Z',
finishedAt: '2023-01-02T00:00:00Z',
summary: {
occurrencesIntroduced: 5,
occurrencesResolved: 3,
occurrencesSuppressed: 0,
occurrenceDistributionByAnalyzer: [],
occurrenceDistributionByCategory: [],
},
repository: {
id: 'repo1',
name: 'Test Project',
},
});
// Mock error response
const errorResponse = {
data: {
errors: [{ message: 'GraphQL Error' }],
},
};
// Mock the checks list response to return an error
mockAxiosInstance.post.mockResolvedValueOnce(errorResponse);
// Execute and expect error
await expect(client.getRecentRunIssues('test-project', 'main')).rejects.toThrow(
'GraphQL Errors: GraphQL Error'
);
});
it('should handle GraphQL errors in occurrences fetch', async () => {
// Mock findMostRecentRun to succeed
vi.spyOn(
client as unknown as { findMostRecentRun: ReturnType<typeof vi.fn> },
'findMostRecentRun'
).mockResolvedValue({
id: 'run1',
runUid: 'run-uid-1',
commitOid: 'commit1',
branchName: 'main',
baseOid: 'base1',
status: 'SUCCESS' as const,
createdAt: '2023-01-02T00:00:00Z',
updatedAt: '2023-01-02T00:00:00Z',
finishedAt: '2023-01-02T00:00:00Z',
summary: {
occurrencesIntroduced: 5,
occurrencesResolved: 3,
occurrencesSuppressed: 0,
occurrenceDistributionByAnalyzer: [],
occurrenceDistributionByCategory: [],
},
repository: {
id: 'repo1',
name: 'Test Project',
},
});
// Mock the initial checks fetch response
const checksListResponse = {
data: {
data: {
run: {
checks: {
edges: [
{
node: {
id: 'check1',
analyzer: {
shortcode: 'javascript',
},
},
},
],
pageInfo: {
hasNextPage: false,
endCursor: null,
},
},
},
},
},
};
// Mock error response
const errorResponse = {
data: {
errors: [{ message: 'GraphQL Error in occurrences' }],
},
};
// Mock the checks list to succeed but occurrences fetch to fail
mockAxiosInstance.post.mockResolvedValueOnce(checksListResponse);
mockAxiosInstance.post.mockResolvedValueOnce(errorResponse);
// Execute and expect error
await expect(client.getRecentRunIssues('test-project', 'main')).rejects.toThrow(
'GraphQL Errors: GraphQL Error in occurrences'
);
});
it('should handle missing node data in occurrences response', async () => {
// Mock findMostRecentRun to succeed
vi.spyOn(
client as unknown as { findMostRecentRun: ReturnType<typeof vi.fn> },
'findMostRecentRun'
).mockResolvedValue({
id: 'run1',
runUid: 'run-uid-1',
commitOid: 'commit1',
branchName: 'main',
baseOid: 'base1',
status: 'SUCCESS' as const,
createdAt: '2023-01-02T00:00:00Z',
updatedAt: '2023-01-02T00:00:00Z',
finishedAt: '2023-01-02T00:00:00Z',
summary: {
occurrencesIntroduced: 5,
occurrencesResolved: 3,
occurrencesSuppressed: 0,
occurrenceDistributionByAnalyzer: [],
occurrenceDistributionByCategory: [],
},
repository: {
id: 'repo1',
name: 'Test Project',
},
});
// Mock the initial checks fetch response
const checksListResponse = {
data: {
data: {
run: {
checks: {
edges: [
{
node: {
id: 'check1',
analyzer: {
shortcode: 'javascript',
},
},
},
],
pageInfo: {
hasNextPage: false,
endCursor: null,
},
},
},
},
},
};
// Mock empty occurrences response with no node data
const emptyOccurrencesResponse = {
data: {
data: {
node: null,
},
},
};
// Mock the responses
mockAxiosInstance.post.mockResolvedValueOnce(checksListResponse);
mockAxiosInstance.post.mockResolvedValueOnce(emptyOccurrencesResponse);
// Execute
const result = await client.getRecentRunIssues('test-project', 'main');
// Verify - should return empty issues when node is null
expect(result.items).toHaveLength(0);
expect(result.run.runUid).toBe('run-uid-1');
expect(result.pageInfo.hasNextPage).toBe(false);
expect(result.totalCount).toBe(0);
});
});
});