Skip to content

Commit af146d9

Browse files
committed
chore(ai): tests
1 parent bf509d8 commit af146d9

4 files changed

Lines changed: 492 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { streamSearchText } from './streamSearchText';
3+
4+
const convertToModelMessages = vi.hoisted(() => vi.fn());
5+
const createUIMessageStream = vi.hoisted(() => vi.fn());
6+
const streamText = vi.hoisted(() => vi.fn());
7+
const stepCountIs = vi.hoisted(() => vi.fn());
8+
const generateSearchSystemPrompt = vi.hoisted(() => vi.fn());
9+
const createSearchTools = vi.hoisted(() => vi.fn());
10+
11+
vi.mock('ai', () => ({
12+
convertToModelMessages: (...args: unknown[]) => convertToModelMessages(...args),
13+
createUIMessageStream: (...args: unknown[]) => createUIMessageStream(...args),
14+
streamText: (...args: unknown[]) => streamText(...args),
15+
stepCountIs: (...args: unknown[]) => stepCountIs(...args),
16+
}));
17+
18+
vi.mock('./prompts/search', () => ({
19+
generateSearchSystemPrompt: (...args: unknown[]) => generateSearchSystemPrompt(...args),
20+
}));
21+
22+
vi.mock('./tools', () => ({
23+
createSearchTools: (...args: unknown[]) => createSearchTools(...args),
24+
}));
25+
26+
describe('streamSearchText', () => {
27+
it('returns early when there are no messages', async () => {
28+
// arrange
29+
const writer = { merge: vi.fn() };
30+
31+
convertToModelMessages.mockResolvedValue([]);
32+
createUIMessageStream.mockImplementation(
33+
({ execute }: { execute: (args: { writer: unknown }) => Promise<void> }) => {
34+
return { execute };
35+
}
36+
);
37+
38+
// act
39+
const stream = streamSearchText({
40+
model: {} as never,
41+
searchEngine: {} as never,
42+
messages: [],
43+
}) as unknown as { execute: (args: { writer: unknown }) => Promise<void> };
44+
45+
await stream.execute({ writer });
46+
47+
// assert
48+
expect(streamText).not.toHaveBeenCalled();
49+
});
50+
51+
it('prepends summary and page context and merges stream', async () => {
52+
// arrange
53+
const writer = { merge: vi.fn() };
54+
const uiStream = { toUIMessageStream: vi.fn().mockReturnValue('ui-stream') };
55+
56+
convertToModelMessages.mockResolvedValue([{ role: 'user', content: 'Hello' }]);
57+
generateSearchSystemPrompt.mockReturnValue('system');
58+
stepCountIs.mockReturnValue('stop');
59+
createSearchTools.mockReturnValue({ search: 'tool' });
60+
streamText.mockReturnValue(uiStream);
61+
createUIMessageStream.mockImplementation(
62+
({ execute }: { execute: (args: { writer: unknown }) => Promise<void> }) => {
63+
return { execute };
64+
}
65+
);
66+
67+
// act
68+
const stream = streamSearchText({
69+
model: {} as never,
70+
searchEngine: {} as never,
71+
messages: [{ id: '1' } as never],
72+
currentPage: { origin: 'https://www.example.com', path: '/docs', title: 'Docs' },
73+
summary: 'Previous summary',
74+
}) as unknown as { execute: (args: { writer: unknown }) => Promise<void> };
75+
76+
await stream.execute({ writer });
77+
78+
// assert
79+
expect(generateSearchSystemPrompt).toHaveBeenCalledWith({
80+
siteName: 'example.com',
81+
siteDomain: 'https://www.example.com',
82+
});
83+
expect(streamText).toHaveBeenCalledWith({
84+
model: {} as never,
85+
system: 'system',
86+
messages: [
87+
{ role: 'system', content: 'Context summary of previous conversation: Previous summary' },
88+
{ role: 'system', content: 'The user is currently viewing the page at /docs with title "Docs".' },
89+
{ role: 'user', content: 'Hello' },
90+
],
91+
stopWhen: 'stop',
92+
tools: { search: 'tool' },
93+
temperature: 0.2,
94+
});
95+
expect(writer.merge).toHaveBeenCalledWith('ui-stream');
96+
});
97+
});

packages/ai/src/summarizer.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { SUMMARIZATION_SYSTEM_PROMPT } from './prompts/summarize';
3+
import { summarizeMessages } from './summarizer';
4+
5+
const generateText = vi.hoisted(() => vi.fn());
6+
const convertToModelMessages = vi.hoisted(() => vi.fn());
7+
8+
vi.mock('ai', () => ({
9+
generateText: (...args: unknown[]) => generateText(...args),
10+
convertToModelMessages: (...args: unknown[]) => convertToModelMessages(...args),
11+
}));
12+
13+
describe('summarizeMessages', () => {
14+
it('builds a prompt without previous summary and trims output', async () => {
15+
// arrange
16+
convertToModelMessages.mockResolvedValue([
17+
{ role: 'user', content: 'Hello' },
18+
{
19+
role: 'assistant',
20+
content: [
21+
{ type: 'text', text: 'Hi there' },
22+
{ type: 'tool-call', toolName: 'search' },
23+
],
24+
},
25+
]);
26+
27+
generateText.mockResolvedValue({ text: ' summary text ' });
28+
29+
// act
30+
const result = await summarizeMessages({
31+
model: {} as never,
32+
messages: [{ id: '1' } as never],
33+
});
34+
35+
// assert
36+
expect(generateText).toHaveBeenCalledWith({
37+
model: {} as never,
38+
system: SUMMARIZATION_SYSTEM_PROMPT,
39+
prompt: expect.stringContaining('Conversation:\nUser: Hello\n\nAssistant: Hi there'),
40+
temperature: 0,
41+
});
42+
expect(result).toBe('summary text');
43+
});
44+
45+
it('includes previous summary when provided', async () => {
46+
// arrange
47+
convertToModelMessages.mockResolvedValue([{ role: 'user', content: 'New question' }]);
48+
generateText.mockResolvedValue({ text: 'summary' });
49+
50+
// act
51+
await summarizeMessages({
52+
model: {} as never,
53+
messages: [{ id: '1' } as never],
54+
previousSummary: 'Older summary',
55+
});
56+
57+
// assert
58+
expect(generateText).toHaveBeenCalledWith({
59+
model: {} as never,
60+
system: SUMMARIZATION_SYSTEM_PROMPT,
61+
prompt: expect.stringContaining('Previous Summary:\nOlder summary'),
62+
temperature: 0,
63+
});
64+
});
65+
});

0 commit comments

Comments
 (0)