140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { sendQuery } from './query.js';
|
|
|
|
const okResponse = (body) => ({
|
|
ok: true,
|
|
status: 200,
|
|
json: () => Promise.resolve(body),
|
|
});
|
|
|
|
describe('sendQuery', () => {
|
|
beforeEach(() => {
|
|
vi.stubGlobal('fetch', vi.fn());
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('sends POST to /api/query with notebookId and question', async () => {
|
|
const data = { answer: 'The answer', citations: [] };
|
|
fetch.mockResolvedValue(okResponse(data));
|
|
|
|
const result = await sendQuery('nb-1', 'What is AI?');
|
|
|
|
expect(fetch).toHaveBeenCalledWith('/api/query', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ notebookId: 'nb-1', question: 'What is AI?' }),
|
|
});
|
|
expect(result).toEqual(data);
|
|
});
|
|
|
|
it('returns data without calling onCitationDetails when no citations', async () => {
|
|
const data = { answer: 'Answer', citations: [] };
|
|
fetch.mockResolvedValue(okResponse(data));
|
|
const onCitationDetails = vi.fn();
|
|
|
|
await sendQuery('nb-1', 'Q?', onCitationDetails);
|
|
|
|
expect(onCitationDetails).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns data without calling onCitationDetails when citations is null', async () => {
|
|
const data = { answer: 'Answer', citations: null };
|
|
fetch.mockResolvedValue(okResponse(data));
|
|
const onCitationDetails = vi.fn();
|
|
|
|
await sendQuery('nb-1', 'Q?', onCitationDetails);
|
|
|
|
expect(onCitationDetails).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fetches citation details and calls onCitationDetails', async () => {
|
|
const queryData = {
|
|
answer: 'The answer [1]',
|
|
citations: [
|
|
{ sourceIndex: 1, name: 'doc.pdf', chunkTexts: ['chunk A'] },
|
|
{ sourceIndex: 2, name: 'doc2.pdf', chunkTexts: ['chunk B'] },
|
|
],
|
|
};
|
|
const detailA = { citedSentence: 'A', topicSummary: 'T1' };
|
|
const detailB = { citedSentence: 'B', topicSummary: 'T2' };
|
|
|
|
fetch
|
|
.mockResolvedValueOnce(okResponse(queryData))
|
|
.mockResolvedValueOnce(okResponse(detailA))
|
|
.mockResolvedValueOnce(okResponse(detailB));
|
|
|
|
const onCitationDetails = vi.fn();
|
|
const result = await sendQuery('nb-1', 'Q?', onCitationDetails);
|
|
|
|
expect(result).toEqual(queryData);
|
|
|
|
// Citation detail fetches happen async — wait for the promise
|
|
await vi.waitFor(() => {
|
|
expect(onCitationDetails).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
const details = onCitationDetails.mock.calls[0][0];
|
|
expect(details).toHaveLength(2);
|
|
expect(details[0]).toEqual({ sourceIndex: 1, ...detailA });
|
|
expect(details[1]).toEqual({ sourceIndex: 2, ...detailB });
|
|
});
|
|
|
|
it('sends correct body for each citation detail request', async () => {
|
|
const queryData = {
|
|
answer: 'Answer text',
|
|
citations: [
|
|
{ sourceIndex: 1, name: 'src.pdf', chunkTexts: ['c1', 'c2'] },
|
|
],
|
|
};
|
|
const detail = { citedSentence: 'S' };
|
|
|
|
fetch
|
|
.mockResolvedValueOnce(okResponse(queryData))
|
|
.mockResolvedValueOnce(okResponse(detail));
|
|
|
|
const onCitationDetails = vi.fn();
|
|
await sendQuery('nb-1', 'Q?', onCitationDetails);
|
|
|
|
await vi.waitFor(() => {
|
|
expect(fetch).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
expect(fetch).toHaveBeenCalledWith('/api/citation-detail', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
chunkTexts: ['c1', 'c2'],
|
|
sourceName: 'src.pdf',
|
|
answer: 'Answer text',
|
|
citationIndex: 1,
|
|
}),
|
|
});
|
|
});
|
|
|
|
it('does not fetch citation details when no callback provided', async () => {
|
|
const queryData = {
|
|
answer: 'Answer',
|
|
citations: [{ sourceIndex: 1, name: 'x', chunkTexts: ['c'] }],
|
|
};
|
|
fetch.mockResolvedValue(okResponse(queryData));
|
|
|
|
await sendQuery('nb-1', 'Q?');
|
|
|
|
// Only 1 fetch call (the query itself), no citation detail fetches
|
|
expect(fetch).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('throws on non-ok response from query endpoint', async () => {
|
|
fetch.mockResolvedValue({
|
|
ok: false,
|
|
status: 500,
|
|
json: () => Promise.resolve({ error: 'Internal error' }),
|
|
});
|
|
|
|
await expect(sendQuery('nb-1', 'Q?')).rejects.toThrow('Internal error');
|
|
});
|
|
});
|