Spaces:
Running
Running
File size: 5,313 Bytes
b034029 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | import { afterEach, describe, expect, it, vi } from 'vitest';
import { fetchUsageEventFilterOptions, fetchUsageEvents, fetchUsageIdentities, triggerSync } from './api';
describe('fetchUsageEvents', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
it('loads stable filter options without query params', async () => {
vi.stubGlobal('window', { __APP_BASE_PATH__: undefined });
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ models: ['claude-sonnet'], sources: [{ value: 'source-a', label: 'Provider A' }] }),
} as Response);
const signal = new AbortController().signal;
const response = await fetchUsageEventFilterOptions(signal);
const [url, init] = fetchMock.mock.calls[0];
const parsed = new URL(String(url), 'http://localhost');
expect(response.models).toEqual(['claude-sonnet']);
expect(parsed.pathname).toBe('/api/v1/usage/events/filters');
expect(parsed.search).toBe('');
expect(parsed.searchParams.get('range')).toBeNull();
expect(parsed.searchParams.get('start')).toBeNull();
expect(parsed.searchParams.get('end')).toBeNull();
expect(parsed.searchParams.get('page')).toBeNull();
expect(parsed.searchParams.get('page_size')).toBeNull();
expect(parsed.searchParams.get('model')).toBeNull();
expect(parsed.searchParams.get('source')).toBeNull();
expect(parsed.searchParams.get('result')).toBeNull();
expect(init).toMatchObject({ credentials: 'include', signal });
});
it('passes pagination and server-side filters as query params', async () => {
vi.stubGlobal('window', { __APP_BASE_PATH__: undefined });
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ events: [], models: [], sources: [], total_count: 0, page: 3, page_size: 100, total_pages: 0 }),
} as Response);
const signal = new AbortController().signal;
await fetchUsageEvents('custom', '2026-04-20T00:00:00Z', '2026-04-21T00:00:00Z', signal, {
page: 3,
pageSize: 100,
model: 'claude-sonnet',
source: 'source-a',
result: 'failed',
});
const [url, init] = fetchMock.mock.calls[0];
const parsed = new URL(String(url), 'http://localhost');
expect(parsed.pathname).toBe('/api/v1/usage/events');
expect(parsed.searchParams.get('range')).toBe('custom');
expect(parsed.searchParams.get('start')).toBe('2026-04-20T00:00:00Z');
expect(parsed.searchParams.get('end')).toBe('2026-04-21T00:00:00Z');
expect(parsed.searchParams.get('page')).toBe('3');
expect(parsed.searchParams.get('page_size')).toBe('100');
expect(parsed.searchParams.get('model')).toBe('claude-sonnet');
expect(parsed.searchParams.get('source')).toBe('source-a');
expect(parsed.searchParams.get('result')).toBe('failed');
expect(parsed.searchParams.get('auth_index')).toBeNull();
expect(init).toMatchObject({ credentials: 'include', signal });
});
it('loads unified usage identities for credential stats', async () => {
vi.stubGlobal('window', { __APP_BASE_PATH__: undefined });
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({
identities: [
{
id: 1,
name: 'Claude primary',
auth_type: 2,
auth_type_name: 'apikey',
identity: 'sk-a***1234',
type: 'claude',
provider: 'anthropic',
total_requests: 3,
success_count: 2,
failure_count: 1,
input_tokens: 10,
output_tokens: 20,
reasoning_tokens: 0,
cached_tokens: 0,
total_tokens: 30,
last_aggregated_usage_event_id: 9,
is_deleted: false,
created_at: '2026-05-04T00:00:00Z',
updated_at: '2026-05-04T00:00:00Z',
},
],
}),
} as Response);
const signal = new AbortController().signal;
const response = await fetchUsageIdentities(signal);
const [url, init] = fetchMock.mock.calls[0];
const parsed = new URL(String(url), 'http://localhost');
expect(response.identities[0].identity).toBe('sk-a***1234');
expect(response.identities[0].auth_type).toBe(2);
expect(typeof response.identities[0].auth_type).toBe('number');
expect(parsed.pathname).toBe('/api/v1/usage/identities');
expect(parsed.search).toBe('');
expect(init).toMatchObject({ credentials: 'include', signal });
});
it('posts to the manual sync endpoint', async () => {
vi.stubGlobal('window', { __APP_BASE_PATH__: undefined });
const fetchMock = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ running: true, sync_running: false, last_status: 'completed' }),
} as Response);
const signal = new AbortController().signal;
const response = await triggerSync(signal);
const [url, init] = fetchMock.mock.calls[0];
const parsed = new URL(String(url), 'http://localhost');
expect(response.last_status).toBe('completed');
expect(parsed.pathname).toBe('/api/v1/sync');
expect(init).toMatchObject({ credentials: 'include', method: 'POST', signal });
});
});
|