Files
srbhr--resume-matcher/apps/frontend/tests/api-tracker.test.ts
T
wehub-resource-sync 5bdf4cc89a
Publish Docker Image / publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:36 +08:00

106 lines
3.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
bulkUpdateStatus,
bulkDeleteApplications,
createApplication,
deleteApplication,
updateApplication,
} from '@/lib/api/tracker';
import { llmProviderToKeyProvider } from '@/lib/api/config';
/**
* Tracker API client contracts: the wrappers must hit the right method/URL and
* send the right payloads, and the provider-axis mapping (used by the Settings
* key store) must mirror the backend `_PROVIDER_KEY_MAP`.
*/
describe('llmProviderToKeyProvider', () => {
it('maps gemini to the google key-store slot and passes others through', () => {
expect(llmProviderToKeyProvider('gemini')).toBe('google');
expect(llmProviderToKeyProvider('openai')).toBe('openai');
expect(llmProviderToKeyProvider('anthropic')).toBe('anthropic');
expect(llmProviderToKeyProvider('openai_compatible')).toBe('openai_compatible');
expect(llmProviderToKeyProvider('ollama')).toBe('ollama');
});
});
describe('tracker API client', () => {
let fetchMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
});
const lastCall = () => {
const [url, options] = fetchMock.mock.calls.at(-1)!;
return { url: String(url), options: options as RequestInit };
};
it('bulkUpdateStatus PATCHes /applications/bulk with ids + status', async () => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ message: 'ok', affected: 2 }), { status: 200 })
);
await bulkUpdateStatus(['a', 'b'], 'interview');
const { url, options } = lastCall();
expect(url).toContain('/applications/bulk');
expect(options.method).toBe('PATCH');
expect(JSON.parse(String(options.body))).toEqual({
application_ids: ['a', 'b'],
status: 'interview',
});
});
it('updateApplication PATCHes /applications/{id} with the partial', async () => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ application_id: 'x' }), { status: 200 })
);
await updateApplication('x', { status: 'rejected', position: 0 });
const { url, options } = lastCall();
expect(url).toContain('/applications/x');
expect(options.method).toBe('PATCH');
expect(JSON.parse(String(options.body))).toEqual({ status: 'rejected', position: 0 });
});
it('createApplication POSTs the manual-add payload', async () => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ application_id: 'x' }), { status: 200 })
);
await createApplication({ resume_id: 'r1', job_description: 'JD', status: 'saved' });
const { url, options } = lastCall();
expect(url).toContain('/applications');
expect(options.method).toBe('POST');
expect(JSON.parse(String(options.body))).toMatchObject({ resume_id: 'r1', status: 'saved' });
});
it('deleteApplication DELETEs /applications/{id}', async () => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ message: 'ok', affected: 1 }), { status: 200 })
);
await deleteApplication('x');
const { url, options } = lastCall();
expect(url).toContain('/applications/x');
expect(options.method).toBe('DELETE');
});
it('bulkDeleteApplications POSTs to /applications/bulk-delete', async () => {
fetchMock.mockResolvedValue(
new Response(JSON.stringify({ message: 'ok', affected: 2 }), { status: 200 })
);
await bulkDeleteApplications(['a', 'b']);
const { url, options } = lastCall();
expect(url).toContain('/applications/bulk-delete');
expect(options.method).toBe('POST');
expect(JSON.parse(String(options.body))).toEqual({ application_ids: ['a', 'b'] });
});
it('surfaces the backend detail message on failure', async () => {
fetchMock.mockResolvedValue(new Response(JSON.stringify({ detail: 'boom' }), { status: 500 }));
await expect(deleteApplication('x')).rejects.toThrow('boom');
});
});