Skip to content

Commit c8cb5e3

Browse files
committed
test: add unit tests for background-refresh service
1 parent 41222eb commit c8cb5e3

4 files changed

Lines changed: 103 additions & 2 deletions

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { BackgroundRefresh } from './background-refresh';
3+
import { getFullDashboardData } from '../../lib/github';
4+
5+
vi.mock('../../lib/github', () => ({
6+
getFullDashboardData: vi.fn(),
7+
}));
8+
9+
describe('BackgroundRefresh Unit Tests', () => {
10+
let service: BackgroundRefresh;
11+
12+
beforeEach(() => {
13+
service = BackgroundRefresh.getInstance();
14+
service.reset();
15+
vi.clearAllMocks();
16+
});
17+
18+
it('behaves as a singleton and returns the same instance', () => {
19+
const anotherInstance = BackgroundRefresh.getInstance();
20+
expect(service).toBe(anotherInstance);
21+
});
22+
23+
describe('isStale', () => {
24+
it('returns true when lastSyncedAt is undefined', () => {
25+
expect(service.isStale(undefined)).toBe(true);
26+
});
27+
28+
it('returns true when lastSyncedAt is an invalid date string', () => {
29+
expect(service.isStale('invalid-date')).toBe(true);
30+
});
31+
32+
it('returns true when lastSyncedAt is older than 10 minutes', () => {
33+
const elevenMinutesAgo = new Date(Date.now() - 11 * 60 * 1000).toISOString();
34+
expect(service.isStale(elevenMinutesAgo)).toBe(true);
35+
});
36+
37+
it('returns false when lastSyncedAt is within 10 minutes', () => {
38+
const nineMinutesAgo = new Date(Date.now() - 9 * 60 * 1000).toISOString();
39+
expect(service.isStale(nineMinutesAgo)).toBe(false);
40+
});
41+
});
42+
43+
describe('triggerRefresh', () => {
44+
it('sanitizes username (trims and converts to lowercase) and sets job active', async () => {
45+
vi.mocked(getFullDashboardData).mockResolvedValue({} as never);
46+
47+
service.triggerRefresh(' TestUser ');
48+
49+
expect(service.isJobActive('testuser')).toBe(true);
50+
expect(getFullDashboardData).toHaveBeenCalledWith(' TestUser ', { bypassCache: true });
51+
52+
// Allow promise microtask to resolve
53+
await new Promise((resolve) => setTimeout(resolve, 0));
54+
expect(service.isJobActive('testuser')).toBe(false);
55+
});
56+
57+
it('prevents concurrent duplicate jobs for the same user', () => {
58+
vi.mocked(getFullDashboardData).mockReturnValue(new Promise(() => {})); // Never resolves
59+
60+
service.triggerRefresh('user1');
61+
service.triggerRefresh('user1');
62+
63+
expect(service.isJobActive('user1')).toBe(true);
64+
expect(getFullDashboardData).toHaveBeenCalledTimes(1);
65+
});
66+
67+
it('removes the user from active jobs on failure', async () => {
68+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
69+
vi.mocked(getFullDashboardData).mockRejectedValue(new Error('Network error'));
70+
71+
service.triggerRefresh('user-fail');
72+
expect(service.isJobActive('user-fail')).toBe(true);
73+
74+
// Allow promise microtask to reject and run finally block
75+
await new Promise((resolve) => setTimeout(resolve, 0));
76+
77+
expect(service.isJobActive('user-fail')).toBe(false);
78+
expect(consoleErrorSpy).toHaveBeenCalled();
79+
consoleErrorSpy.mockRestore();
80+
});
81+
});
82+
83+
describe('reset', () => {
84+
it('clears all currently active jobs', () => {
85+
vi.mocked(getFullDashboardData).mockReturnValue(new Promise(() => {})); // Never resolves
86+
service.triggerRefresh('active-user');
87+
expect(service.isJobActive('active-user')).toBe(true);
88+
89+
service.reset();
90+
expect(service.isJobActive('active-user')).toBe(false);
91+
});
92+
});
93+
});

services/github/background-refresh.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class BackgroundRefresh {
2525

2626
try {
2727
const lastSyncTime = new Date(lastSyncedAt).getTime();
28+
if (isNaN(lastSyncTime)) return true;
2829
return Date.now() - lastSyncTime > STALE_THRESHOLD_MS;
2930
} catch {
3031
return true;

services/github/background-refresh.type-compiler.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ describe('BackgroundRefresh type compiler validation', () => {
4141

4242
expect(backgroundRefresh.isStale(staleDate)).toBe(true);
4343
expect(backgroundRefresh.isStale(freshDate)).toBe(false);
44-
expect(backgroundRefresh.isStale('invalid-date')).toBe(false);
44+
expect(backgroundRefresh.isStale('invalid-date')).toBe(true);
4545
});
4646
});

vercel.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
{
2-
"ignoreCommand": "bash vercel-ignore.sh"
2+
"ignoreCommand": "bash vercel-ignore.sh",
3+
"git": {
4+
"deploymentEnabled": {
5+
"main": true,
6+
"fix/issue-*": false,
7+
"test/*": false
8+
}
9+
}
310
}

0 commit comments

Comments
 (0)