141 lines
5.4 KiB
TypeScript
141 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const hostApi = vi.hoisted(() => ({
|
|
fetch: vi.fn(),
|
|
ensureToken: vi.fn(),
|
|
createEvents: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('@/lib/host-api', () => ({
|
|
hostApiFetch: hostApi.fetch,
|
|
ensureHostApiToken: hostApi.ensureToken,
|
|
createHostEventSource: hostApi.createEvents,
|
|
}));
|
|
|
|
describe('coding Conversations Host facade', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('loads the target snapshot through the encoded Host route', async () => {
|
|
const snapshot = { conversation: { id: 'conversation/a' } };
|
|
hostApi.fetch.mockResolvedValueOnce({ snapshot });
|
|
const { getCodingConversationSnapshot } = await import('@/lib/coding-conversations');
|
|
|
|
await expect(getCodingConversationSnapshot('conversation/a')).resolves.toBe(snapshot);
|
|
expect(hostApi.fetch).toHaveBeenCalledWith(
|
|
'/api/coding/conversations/conversation%2Fa/snapshot',
|
|
);
|
|
});
|
|
|
|
it('submits only the product prompt DTO and returns its acceptance', async () => {
|
|
const acceptance = {
|
|
accepted: true,
|
|
conversationId: 'conversation/a',
|
|
clientRequestId: 'request-1',
|
|
runId: 'run-1',
|
|
mode: 'prompt',
|
|
};
|
|
hostApi.fetch.mockResolvedValueOnce({ acceptance });
|
|
const { submitCodingConversationPrompt } = await import('@/lib/coding-conversations');
|
|
|
|
await expect(submitCodingConversationPrompt({
|
|
conversationId: 'conversation/a',
|
|
clientRequestId: 'request-1',
|
|
mode: 'prompt',
|
|
text: 'Build it',
|
|
attachments: [{ attachmentId: 'attachment-1' }],
|
|
})).resolves.toBe(acceptance);
|
|
expect(hostApi.fetch).toHaveBeenCalledWith(
|
|
'/api/coding/conversations/conversation%2Fa/prompt',
|
|
{
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
clientRequestId: 'request-1',
|
|
mode: 'prompt',
|
|
text: 'Build it',
|
|
attachments: [{ attachmentId: 'attachment-1' }],
|
|
}),
|
|
},
|
|
);
|
|
});
|
|
|
|
it('hydrates the Host token before constructing the EventSource URL', async () => {
|
|
const order: string[] = [];
|
|
const source = { close: vi.fn() } as unknown as EventSource;
|
|
hostApi.ensureToken.mockImplementationOnce(async () => {
|
|
order.push('token');
|
|
return 'host-token';
|
|
});
|
|
hostApi.createEvents.mockImplementationOnce((path: string) => {
|
|
order.push(`events:${path}`);
|
|
return source;
|
|
});
|
|
const { openCodingConversationEvents } = await import('@/lib/coding-conversations');
|
|
|
|
await expect(openCodingConversationEvents('conversation/a')).resolves.toBe(source);
|
|
expect(order).toEqual([
|
|
'token',
|
|
'events:/api/coding/events?conversationId=conversation%2Fa',
|
|
]);
|
|
});
|
|
|
|
it('routes every PI-130 Conversation control through the product Host surface', async () => {
|
|
const model = {
|
|
model: { accountId: 'account/1', modelId: 'model/1', thinkingLevel: 'medium' },
|
|
modelResolution: 'resolved',
|
|
};
|
|
const forked = { id: 'forked-1' };
|
|
const diagnostics = { revision: { provider: 1, resources: 2 }, workers: [] };
|
|
hostApi.fetch
|
|
.mockResolvedValueOnce({})
|
|
.mockResolvedValueOnce({ model })
|
|
.mockResolvedValueOnce({ model })
|
|
.mockResolvedValueOnce({})
|
|
.mockResolvedValueOnce({})
|
|
.mockResolvedValueOnce({ conversation: forked })
|
|
.mockResolvedValueOnce({ interactions: [{ id: 'interaction-1' }] })
|
|
.mockResolvedValueOnce({})
|
|
.mockResolvedValueOnce({ runtime: diagnostics });
|
|
const facade = await import('@/lib/coding-conversations');
|
|
|
|
await facade.abortCodingConversation('conversation/a');
|
|
await expect(facade.setCodingConversationModel('conversation/a', model.model)).resolves.toBe(model);
|
|
await expect(facade.setCodingConversationThinking('conversation/a', 'high')).resolves.toBe(model);
|
|
await facade.compactCodingConversation('conversation/a');
|
|
await facade.recoverCodingConversation('conversation/a');
|
|
await expect(facade.forkCodingConversation('conversation/a', 'entry/1')).resolves.toBe(forked);
|
|
await facade.listCodingConversationInteractions('conversation/a');
|
|
await facade.respondCodingConversationInteraction('conversation/a', {
|
|
interactionId: 'interaction/1',
|
|
optionId: 'option-1',
|
|
});
|
|
await expect(facade.getCodingRuntimeDiagnostics()).resolves.toBe(diagnostics);
|
|
|
|
expect(hostApi.fetch.mock.calls).toEqual([
|
|
['/api/coding/conversations/conversation%2Fa/abort', { method: 'POST', body: '{}' }],
|
|
['/api/coding/conversations/conversation%2Fa/model', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ model: model.model }),
|
|
}],
|
|
['/api/coding/conversations/conversation%2Fa/thinking', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ thinkingLevel: 'high' }),
|
|
}],
|
|
['/api/coding/conversations/conversation%2Fa/compact', { method: 'POST', body: '{}' }],
|
|
['/api/coding/conversations/conversation%2Fa/recover', { method: 'POST', body: '{}' }],
|
|
['/api/coding/conversations/conversation%2Fa/fork', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ sourceEntryId: 'entry/1' }),
|
|
}],
|
|
['/api/coding/interactions?conversationId=conversation%2Fa'],
|
|
['/api/coding/interactions/interaction%2F1/respond', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ conversationId: 'conversation/a', optionId: 'option-1' }),
|
|
}],
|
|
['/api/coding/runtime/diagnostics'],
|
|
]);
|
|
expect(JSON.stringify(hostApi.fetch.mock.calls)).not.toContain('/api/opencode');
|
|
});
|
|
});
|