import { beforeEach, describe, expect, it, vi } from 'vitest'; const hostApiFetch = vi.hoisted(() => vi.fn()); vi.mock('@/lib/host-api', () => ({ hostApiFetch })); describe('coding project Host facade', () => { beforeEach(() => vi.clearAllMocks()); it('uses only the vendor-neutral project metadata routes', async () => { hostApiFetch .mockResolvedValueOnce({ projects: [], activeProjectId: null }) .mockResolvedValueOnce({ snapshot: { project: { id: 'project/a' }, config: {} } }) .mockResolvedValueOnce({ conversations: [] }) .mockResolvedValueOnce({ conversation: { id: 'conversation-1' } }) .mockResolvedValueOnce({ conversation: { id: 'conversation-1', title: 'Renamed' } }) .mockResolvedValueOnce({}); const { createCodingProjectConversation, getCodingProjectConfig, listCodingProjectConversations, listCodingProjects, patchCodingProjectConversation, deleteCodingProjectConversation, } = await import('@/lib/coding-projects'); await listCodingProjects(); await getCodingProjectConfig('project/a'); await listCodingProjectConversations('project/a'); await createCodingProjectConversation({ projectId: 'project/a', agentId: 'agent-1', title: '新对话', }); await patchCodingProjectConversation('conversation/1', { title: 'Renamed', unread: true }); await deleteCodingProjectConversation('conversation/1'); expect(hostApiFetch.mock.calls).toEqual([ ['/api/coding/projects'], ['/api/coding/projects/config?projectId=project%2Fa'], ['/api/coding/projects/conversations?projectId=project%2Fa'], ['/api/coding/projects/conversations', { method: 'POST', body: JSON.stringify({ projectId: 'project/a', agentId: 'agent-1', title: '新对话' }), }], ['/api/coding/conversations/conversation%2F1', { method: 'PATCH', body: JSON.stringify({ title: 'Renamed', unread: true }), }], ['/api/coding/conversations/conversation%2F1', { method: 'DELETE' }], ]); }); it('sends explicit identity choices and a literal independent-copy confirmation', async () => { hostApiFetch .mockResolvedValueOnce({ snapshot: { project: { id: 'local-1' }, config: { projectId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, knowledgeFiles: [] } }) .mockResolvedValueOnce({ snapshot: { project: { id: 'local-1' }, config: { projectId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb' }, knowledgeFiles: [] } }) .mockResolvedValueOnce({ snapshot: { project: { id: 'local-1' }, config: { projectId: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc' }, knowledgeFiles: [] } }); const { createCodingProject, isCanonicalCodingProjectId, makeCodingProjectIndependentCopy, resolveCodingProjectIdentity, } = await import('@/lib/coding-projects'); await createCodingProject({ projectPath: 'C:/projects/new', projectType: 'custom', identity: { kind: 'create' }, }); await resolveCodingProjectIdentity('local-1', { kind: 'bind', projectId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', }); await makeCodingProjectIndependentCopy('local-1'); expect(isCanonicalCodingProjectId('bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb')).toBe(true); expect(isCanonicalCodingProjectId('BBBBBBBB-BBBB-4BBB-8BBB-BBBBBBBBBBBB')).toBe(false); expect(isCanonicalCodingProjectId('not-a-project-id')).toBe(false); expect(hostApiFetch.mock.calls).toEqual([ ['/api/coding/projects/create', { method: 'POST', body: JSON.stringify({ projectPath: 'C:/projects/new', projectType: 'custom', identity: { kind: 'create' }, }), }], ['/api/coding/projects/identity', { method: 'POST', body: JSON.stringify({ localProjectId: 'local-1', identity: { kind: 'bind', projectId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb' }, }), }], ['/api/coding/projects/identity/independent-copy', { method: 'POST', body: JSON.stringify({ localProjectId: 'local-1', confirmed: true }), }], ]); }); });