96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|||
|
|
import { usePrContextStore, getPrContextKey } from './usePrContextStore';
|
||
|
|
import type { GitHubAPI, GitHubPullRequestContextResult } from '@/lib/api/types';
|
||
|
|
|
||
|
|
const makeGithub = (result: GitHubPullRequestContextResult) => {
|
||
|
|
let calls = 0;
|
||
|
|
const prContext = async () => {
|
||
|
|
calls += 1;
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
return { github: { prContext } as unknown as GitHubAPI, callCount: () => calls };
|
||
|
|
};
|
||
|
|
|
||
|
|
const RESULT: GitHubPullRequestContextResult = {
|
||
|
|
connected: true,
|
||
|
|
pr: null,
|
||
|
|
issueComments: [],
|
||
|
|
reviewComments: [],
|
||
|
|
};
|
||
|
|
|
||
|
|
describe('usePrContextStore', () => {
|
||
|
|
test('caches within TTL and dedupes plain requests after a detailed one', async () => {
|
||
|
|
const { github, callCount } = makeGithub(RESULT);
|
||
|
|
const { ensure } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
const first = await ensure(github, '/repo-a', 1, { includeCheckDetails: true });
|
||
|
|
expect(first).toEqual(RESULT);
|
||
|
|
expect(callCount()).toBe(1);
|
||
|
|
|
||
|
|
// Fresh cache satisfies both detailed and plain requests.
|
||
|
|
await ensure(github, '/repo-a', 1, { includeCheckDetails: true });
|
||
|
|
await ensure(github, '/repo-a', 1);
|
||
|
|
expect(callCount()).toBe(1);
|
||
|
|
|
||
|
|
// force bypasses the cache.
|
||
|
|
await ensure(github, '/repo-a', 1, { includeCheckDetails: true, force: true });
|
||
|
|
expect(callCount()).toBe(2);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('a plain cached result does not satisfy a detailed request', async () => {
|
||
|
|
const { github, callCount } = makeGithub(RESULT);
|
||
|
|
const { ensure } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
await ensure(github, '/repo-b', 2);
|
||
|
|
await ensure(github, '/repo-b', 2, { includeCheckDetails: true });
|
||
|
|
expect(callCount()).toBe(2);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('concurrent callers share one in-flight request', async () => {
|
||
|
|
const { github, callCount } = makeGithub(RESULT);
|
||
|
|
const { ensure } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
const [a, b] = await Promise.all([
|
||
|
|
ensure(github, '/repo-c', 3, { includeCheckDetails: true }),
|
||
|
|
ensure(github, '/repo-c', 3, { includeCheckDetails: true }),
|
||
|
|
]);
|
||
|
|
expect(a).toEqual(RESULT);
|
||
|
|
expect(b).toEqual(RESULT);
|
||
|
|
expect(callCount()).toBe(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('invalidate clears the entry so the next ensure refetches', async () => {
|
||
|
|
const { github, callCount } = makeGithub(RESULT);
|
||
|
|
const { ensure, invalidate } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
await ensure(github, '/repo-d', 4);
|
||
|
|
invalidate('/repo-d', 4);
|
||
|
|
expect(usePrContextStore.getState().entries[getPrContextKey('/repo-d', 4)] ?? null).toBe(null);
|
||
|
|
await ensure(github, '/repo-d', 4);
|
||
|
|
expect(callCount()).toBe(2);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('invalidate matches the directory exactly, not by prefix', async () => {
|
||
|
|
const { github } = makeGithub(RESULT);
|
||
|
|
const { ensure, invalidate } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
await ensure(github, '/repo', 1);
|
||
|
|
await ensure(github, '/repo-beta', 1);
|
||
|
|
invalidate('/repo');
|
||
|
|
|
||
|
|
expect(usePrContextStore.getState().entries[getPrContextKey('/repo', 1)] ?? null).toBe(null);
|
||
|
|
expect(usePrContextStore.getState().entries[getPrContextKey('/repo-beta', 1)]?.result).toEqual(RESULT);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('failed fetch records the error and returns null', async () => {
|
||
|
|
const github = { prContext: async () => { throw new Error('boom'); } } as unknown as GitHubAPI;
|
||
|
|
const { ensure } = usePrContextStore.getState();
|
||
|
|
|
||
|
|
const result = await ensure(github, '/repo-e', 5);
|
||
|
|
expect(result).toBeNull();
|
||
|
|
const entry = usePrContextStore.getState().entries[getPrContextKey('/repo-e', 5)];
|
||
|
|
expect(entry?.error).toBe('boom');
|
||
|
|
expect(entry?.isLoading).toBe(false);
|
||
|
|
});
|
||
|
|
});
|