Files
makelore/tests/unit/coding-conversations-facade.test.ts

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');
});
});