deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
340 lines (275 loc) • 10.8 kB
text/typescript
/**
* @fileoverview Tests for ProjectRepository
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { ProjectRepository } from '../project.repository.js';
import { ProjectsClient } from '../../../client/projects-client.js';
import { DeepSourceProject } from '../../../models/projects.js';
import { asProjectKey } from '../../../types/branded.js';
import { Project } from '../../../domain/aggregates/project/project.aggregate.js';
// Mock the ProjectsClient
vi.mock('../../../client/projects-client.js');
// Mock the logger
vi.mock('../../../utils/logging/logger.js', () => ({
createLogger: vi.fn(() => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
})),
}));
describe('ProjectRepository', () => {
let repository: ProjectRepository;
let mockProjectsClient: anyed<ProjectsClient>;
let mockApiProjects: DeepSourceProject[];
beforeEach(() => {
// Reset all mocks
vi.clearAllMocks();
// Create mock ProjectsClient with mocked methods
mockProjectsClient = {
listProjects: vi.fn(),
} as unknown as anyed<ProjectsClient>;
// Create test data
mockApiProjects = [
{
key: asProjectKey('project-1'),
name: 'Project One',
repository: {
url: 'https://github.com/user/project-1',
provider: 'GITHUB',
login: 'user',
isPrivate: false,
isActivated: true,
},
},
{
key: asProjectKey('project-2'),
name: 'Project Two',
repository: {
url: 'https://gitlab.com/user/project-2',
provider: 'GITLAB',
login: 'user',
isPrivate: true,
isActivated: false,
},
},
{
key: asProjectKey('project-3'),
name: 'Project Three',
repository: {
url: 'https://github.com/org/project-3',
provider: 'GITHUB',
login: 'org',
isPrivate: false,
isActivated: true,
},
},
];
// Setup default mock behavior
mockProjectsClient.listProjects.mockResolvedValue(mockApiProjects);
// Create repository instance
repository = new ProjectRepository(mockProjectsClient);
});
describe('findById', () => {
it('should find project by id', async () => {
const projectKey = asProjectKey('project-1');
const project = await repository.findById(projectKey);
expect(project).not.toBeNull();
expect(project?.key).toBe(projectKey);
expect(project?.name).toBe('Project One');
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should return null when project not found', async () => {
const projectKey = asProjectKey('non-existent');
const project = await repository.findById(projectKey);
expect(project).toBeNull();
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should propagate client errors', async () => {
const error = new Error('API Error');
mockProjectsClient.listProjects.mockRejectedValue(error);
await expect(repository.findById(asProjectKey('project-1'))).rejects.toThrow('API Error');
});
});
describe('findByKey', () => {
it('should find project by key', async () => {
const projectKey = asProjectKey('project-2');
const project = await repository.findByKey(projectKey);
expect(project).not.toBeNull();
expect(project?.key).toBe(projectKey);
expect(project?.name).toBe('Project Two');
expect(project?.repository.provider).toBe('GITLAB');
expect(project?.status).toBe('INACTIVE');
});
it('should map activated projects to ACTIVE status', async () => {
const projectKey = asProjectKey('project-1');
const project = await repository.findByKey(projectKey);
expect(project).not.toBeNull();
expect(project?.status).toBe('ACTIVE');
expect(project?.configuration.isActivated).toBe(true);
});
it('should map non-activated projects to INACTIVE status', async () => {
const projectKey = asProjectKey('project-2');
const project = await repository.findByKey(projectKey);
expect(project).not.toBeNull();
expect(project?.status).toBe('INACTIVE');
expect(project?.configuration.isActivated).toBe(false);
});
});
describe('findAll', () => {
it('should return all projects', async () => {
const projects = await repository.findAll();
expect(projects).toHaveLength(3);
expect(projects[0]).toBeInstanceOf(Project);
expect(projects.map((p) => p.key)).toEqual([
asProjectKey('project-1'),
asProjectKey('project-2'),
asProjectKey('project-3'),
]);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should return empty array when no projects exist', async () => {
mockProjectsClient.listProjects.mockResolvedValue([]);
const projects = await repository.findAll();
expect(projects).toEqual([]);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should propagate client errors', async () => {
const error = new Error('Network Error');
mockProjectsClient.listProjects.mockRejectedValue(error);
await expect(repository.findAll()).rejects.toThrow('Network Error');
});
});
describe('findActive', () => {
it('should return only active projects', async () => {
const projects = await repository.findActive();
expect(projects).toHaveLength(2);
expect(projects.map((p) => p.key)).toEqual([
asProjectKey('project-1'),
asProjectKey('project-3'),
]);
expect(projects.every((p) => p.status === 'ACTIVE')).toBe(true);
});
it('should return empty array when no active projects exist', async () => {
// Mock all projects as inactive
const inactiveProjects = mockApiProjects.map((p) => ({
...p,
repository: { ...p.repository, isActivated: false },
}));
mockProjectsClient.listProjects.mockResolvedValue(inactiveProjects);
const projects = await repository.findActive();
expect(projects).toEqual([]);
});
});
describe('findByProvider', () => {
it('should return projects for specific provider', async () => {
const projects = await repository.findByProvider('GITHUB');
expect(projects).toHaveLength(2);
expect(projects.map((p) => p.key)).toEqual([
asProjectKey('project-1'),
asProjectKey('project-3'),
]);
expect(projects.every((p) => p.repository.provider === 'GITHUB')).toBe(true);
});
it('should return empty array for unknown provider', async () => {
const projects = await repository.findByProvider('BITBUCKET');
expect(projects).toEqual([]);
});
it('should handle GITLAB provider', async () => {
const projects = await repository.findByProvider('GITLAB');
expect(projects).toHaveLength(1);
expect(projects[0].key).toBe(asProjectKey('project-2'));
expect(projects[0].repository.provider).toBe('GITLAB');
});
});
describe('exists', () => {
it('should return true when project exists', async () => {
const exists = await repository.exists(asProjectKey('project-1'));
expect(exists).toBe(true);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should return false when project does not exist', async () => {
const exists = await repository.exists(asProjectKey('non-existent'));
expect(exists).toBe(false);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should propagate client errors', async () => {
const error = new Error('API Error');
mockProjectsClient.listProjects.mockRejectedValue(error);
await expect(repository.exists(asProjectKey('project-1'))).rejects.toThrow('API Error');
});
});
describe('count', () => {
it('should return total project count', async () => {
const count = await repository.count();
expect(count).toBe(3);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should return 0 when no projects exist', async () => {
mockProjectsClient.listProjects.mockResolvedValue([]);
const count = await repository.count();
expect(count).toBe(0);
});
});
describe('countActive', () => {
it('should return active project count', async () => {
const count = await repository.countActive();
expect(count).toBe(2);
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(1);
});
it('should return 0 when no active projects exist', async () => {
// Mock all projects as inactive
const inactiveProjects = mockApiProjects.map((p) => ({
...p,
repository: { ...p.repository, isActivated: false },
}));
mockProjectsClient.listProjects.mockResolvedValue(inactiveProjects);
const count = await repository.countActive();
expect(count).toBe(0);
});
});
describe('save', () => {
it('should throw error indicating operation not supported', async () => {
const project = Project.create({
key: asProjectKey('new-project'),
name: 'New Project',
repository: {
url: 'https://github.com/user/new-project',
provider: 'GITHUB',
login: 'user',
isPrivate: false,
},
});
await expect(repository.save(project)).rejects.toThrow(
'Save operation is not supported by DeepSource API'
);
});
});
describe('delete', () => {
it('should throw error indicating operation not supported', async () => {
await expect(repository.delete(asProjectKey('project-1'))).rejects.toThrow(
'Delete operation is not supported by DeepSource API'
);
});
});
describe('data freshness', () => {
it('should fetch fresh data on every request', async () => {
// First call
await repository.findByKey(asProjectKey('project-1'));
// Update mock data
mockApiProjects[0].name = 'Updated Project One';
// Second call should get fresh data
const project = await repository.findByKey(asProjectKey('project-1'));
expect(project?.name).toBe('Updated Project One');
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(2);
});
it('should not cache results between different method calls', async () => {
await repository.findAll();
await repository.findActive();
await repository.findByProvider('GITHUB');
await repository.count();
await repository.countActive();
expect(mockProjectsClient.listProjects).toHaveBeenCalledTimes(5);
});
});
});