Rednote / frontend /src /api /__tests__ /client.test.ts
orakzai-io
Initial
194aaa5
Raw History Blame Contribute Delete
4.62 kB
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockFetch = vi.fn();
global.fetch = mockFetch;
// Set VITE_API_URL for tests
import.meta.env.VITE_API_URL = 'http://localhost:8000';
import {
fetchDocuments,
uploadDocument,
deleteDocument,
streamChat,
} from '../client';
// Mock auth headers
vi.mock('../../lib/auth', () => ({
getAuthHeaders: vi.fn(() => ({ Authorization: 'Bearer test-token' })),
}));
describe('API client', () => {
beforeEach(() => {
vi.clearAllMocks();
mockFetch.mockReset();
});
// ── fetchDocuments ──
describe('fetchDocuments', () => {
it('fetches documents list with auth header', async () => {
const docs = [
{ id: '1', user_id: 'u1', filename: 'doc1.pdf', status: 'ready', created_at: '2025-01-01' },
];
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => docs,
});
const result = await fetchDocuments();
expect(result).toEqual(docs);
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:8000/documents',
expect.objectContaining({
headers: expect.objectContaining({ Authorization: 'Bearer test-token' }),
})
);
});
it('throws on fetch failure', async () => {
mockFetch.mockResolvedValueOnce({ ok: false, json: async () => ({ detail: 'Failed' }) });
await expect(fetchDocuments()).rejects.toThrow('Failed to fetch documents');
});
});
// ── uploadDocument ──
describe('uploadDocument', () => {
it('uploads a file as FormData', async () => {
const file = new File(['test content'], 'test.txt', { type: 'text/plain' });
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({ id: '1', filename: 'test.txt', status: 'ready' }),
});
await uploadDocument(file);
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:8000/documents/upload',
expect.objectContaining({
method: 'POST',
headers: expect.not.objectContaining({ 'Content-Type': 'application/json' }),
})
);
});
it('throws on upload failure with detail', async () => {
const file = new File(['bad'], 'bad.txt', { type: 'text/plain' });
mockFetch.mockResolvedValueOnce({
ok: false,
json: async () => ({ detail: 'Unsupported file' }),
});
await expect(uploadDocument(file)).rejects.toThrow('Unsupported file');
});
});
// ── deleteDocument ──
describe('deleteDocument', () => {
it('sends DELETE request', async () => {
mockFetch.mockResolvedValueOnce({ ok: true });
await deleteDocument('doc-123');
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:8000/documents/doc-123',
expect.objectContaining({ method: 'DELETE' })
);
});
it('throws on delete failure', async () => {
mockFetch.mockResolvedValueOnce({ ok: false });
await expect(deleteDocument('doc-123')).rejects.toThrow('Failed to delete document');
});
});
// ── streamChat ──
describe('streamChat', () => {
function createMockStream(chunks: string[]): ReadableStream {
const encoder = new TextEncoder();
return new ReadableStream({
async start(controller) {
for (const chunk of chunks) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
});
}
it('calls onChunk with streamed content', async () => {
const mockBody = createMockStream(['Hello', ' world']);
mockFetch.mockResolvedValueOnce({
ok: true,
body: mockBody,
});
const onChunk = vi.fn();
const onError = vi.fn();
await streamChat(
{ messages: [{ role: 'user', content: 'Hi' }], document_id: null },
{ onChunk, onError },
);
expect(onChunk).toHaveBeenCalled();
expect(onError).not.toHaveBeenCalled();
});
it('calls onError when request fails', async () => {
mockFetch.mockResolvedValueOnce({
ok: false,
json: async () => ({ detail: 'Chat error' }),
});
const onChunk = vi.fn();
const onError = vi.fn();
await streamChat(
{ messages: [{ role: 'user', content: 'Hi' }], document_id: null },
{ onChunk, onError },
);
expect(onError).toHaveBeenCalledWith('Chat error');
});
});
});