Adds missing server dir
This commit is contained in:
275
server/github.test.ts
Normal file
275
server/github.test.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import axios from 'axios';
|
||||
import { GitHubService } from './github';
|
||||
import { Repository } from './config';
|
||||
|
||||
// Mock axios
|
||||
vi.mock('axios');
|
||||
const mockedAxios = vi.mocked(axios);
|
||||
|
||||
describe('GitHubService', () => {
|
||||
let githubService: GitHubService;
|
||||
const mockToken = 'test-token';
|
||||
const mockRepository: Repository = {
|
||||
owner: 'test-owner',
|
||||
name: 'test-repo'
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
githubService = new GitHubService(mockToken, 300); // 300 seconds cache timeout
|
||||
vi.clearAllMocks();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should initialize with correct cache timeout in seconds', () => {
|
||||
const service = new GitHubService('token', 600);
|
||||
expect(service.getCacheTimeout()).toBe(600);
|
||||
});
|
||||
|
||||
it('should use default cache timeout when not specified', () => {
|
||||
const service = new GitHubService('token');
|
||||
expect(service.getCacheTimeout()).toBe(300);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWorkflowRuns', () => {
|
||||
it('should fetch workflow runs successfully', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
workflow_runs: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test Workflow',
|
||||
display_title: 'Test Run',
|
||||
status: 'completed',
|
||||
conclusion: 'success',
|
||||
created_at: '2024-01-01T10:00:00Z',
|
||||
repository: {
|
||||
id: 1,
|
||||
name: 'test-repo',
|
||||
full_name: 'test-owner/test-repo',
|
||||
owner: {
|
||||
login: 'test-owner',
|
||||
avatar_url: 'https://github.com/test-owner.png'
|
||||
}
|
||||
},
|
||||
actor: {
|
||||
login: 'test-actor',
|
||||
avatar_url: 'https://github.com/test-actor.png'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
headers: {
|
||||
'x-ratelimit-limit': '5000',
|
||||
'x-ratelimit-remaining': '4999',
|
||||
'x-ratelimit-reset': '1640995200',
|
||||
'x-ratelimit-used': '1'
|
||||
}
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const result = await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
expect(mockedAxios.get).toHaveBeenCalledWith(
|
||||
'https://api.github.com/repos/test-owner/test-repo/actions/runs',
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'token test-token',
|
||||
'Accept': 'application/vnd.github.v3+json',
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
},
|
||||
params: {
|
||||
per_page: 1,
|
||||
page: 1
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
expect(result).toEqual(mockResponse.data.workflow_runs);
|
||||
});
|
||||
|
||||
it('should handle API errors gracefully', async () => {
|
||||
mockedAxios.get.mockRejectedValueOnce(new Error('API Error'));
|
||||
|
||||
const result = await githubService.getWorkflowRuns(mockRepository);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle rate limit exceeded', async () => {
|
||||
mockedAxios.get.mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 403,
|
||||
headers: {
|
||||
'x-ratelimit-remaining': '0'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const result = await githubService.getWorkflowRuns(mockRepository);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('caching', () => {
|
||||
it('should cache responses', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
workflow_runs: [{ id: 1, name: 'Test' }]
|
||||
},
|
||||
headers: {
|
||||
'x-ratelimit-remaining': '4999'
|
||||
}
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
// First call
|
||||
const result1 = await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
// Second call should use cache
|
||||
const result2 = await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
expect(mockedAxios.get).toHaveBeenCalledTimes(1);
|
||||
expect(result1).toEqual(result2);
|
||||
});
|
||||
|
||||
it('should expire cache after TTL', async () => {
|
||||
const mockResponse = {
|
||||
data: {
|
||||
workflow_runs: [{ id: 1, name: 'Test' }]
|
||||
},
|
||||
headers: {
|
||||
'x-ratelimit-remaining': '4999'
|
||||
}
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValue(mockResponse);
|
||||
|
||||
// First call
|
||||
await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
// Advance time beyond cache TTL (300 seconds)
|
||||
vi.advanceTimersByTime(301 * 1000);
|
||||
|
||||
// Second call should make new request
|
||||
await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
expect(mockedAxios.get).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should clear cache', async () => {
|
||||
const mockResponse = {
|
||||
data: { workflow_runs: [] },
|
||||
headers: { 'x-ratelimit-remaining': '4999' }
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValue(mockResponse);
|
||||
|
||||
await githubService.getWorkflowRuns(mockRepository, 1);
|
||||
|
||||
const statsBefore = githubService.getCacheStats();
|
||||
expect(statsBefore.size).toBeGreaterThan(0);
|
||||
|
||||
githubService.clearCache();
|
||||
|
||||
const statsAfter = githubService.getCacheStats();
|
||||
expect(statsAfter.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLatestMainBranchRuns', () => {
|
||||
it('should fetch runs from multiple repositories in parallel', async () => {
|
||||
const repositories = [
|
||||
{ owner: 'owner1', name: 'repo1' },
|
||||
{ owner: 'owner2', name: 'repo2' }
|
||||
];
|
||||
|
||||
const mockResponse = {
|
||||
data: {
|
||||
workflow_runs: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Test',
|
||||
repository: {
|
||||
full_name: 'owner1/repo1',
|
||||
owner: { login: 'owner1' }
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
headers: { 'x-ratelimit-remaining': '4999' }
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await githubService.getLatestMainBranchRuns(repositories);
|
||||
|
||||
expect(mockedAxios.get).toHaveBeenCalledTimes(2);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should handle repositories with no runs', async () => {
|
||||
const repositories = [
|
||||
{ owner: 'owner1', name: 'repo1' }
|
||||
];
|
||||
|
||||
const mockResponse = {
|
||||
data: { workflow_runs: [] },
|
||||
headers: { 'x-ratelimit-remaining': '4999' }
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await githubService.getLatestMainBranchRuns(repositories);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('rate limit handling', () => {
|
||||
it('should update rate limit info from response headers', async () => {
|
||||
const mockResponse = {
|
||||
data: { workflow_runs: [] },
|
||||
headers: {
|
||||
'x-ratelimit-limit': '5000',
|
||||
'x-ratelimit-remaining': '4000',
|
||||
'x-ratelimit-reset': '1640995200',
|
||||
'x-ratelimit-used': '1000'
|
||||
}
|
||||
};
|
||||
|
||||
mockedAxios.get.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
await githubService.getWorkflowRuns(mockRepository);
|
||||
|
||||
const rateLimitInfo = githubService.getRateLimitInfo();
|
||||
expect(rateLimitInfo.limit).toBe(5000);
|
||||
expect(rateLimitInfo.remaining).toBe(4000);
|
||||
expect(rateLimitInfo.used).toBe(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateToken', () => {
|
||||
it('should update token without losing cache', () => {
|
||||
const newToken = 'new-token';
|
||||
const initialCacheSize = githubService.getCacheStats().size;
|
||||
|
||||
githubService.updateToken(newToken);
|
||||
|
||||
const finalCacheSize = githubService.getCacheStats().size;
|
||||
expect(finalCacheSize).toBe(initialCacheSize);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCacheTimeout', () => {
|
||||
it('should return cache timeout in seconds', () => {
|
||||
const service = new GitHubService('token', 600);
|
||||
expect(service.getCacheTimeout()).toBe(600);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user