import { beforeEach, describe, expect, it, vi } from 'vitest'; import { WorksSquareDesignWorkspace } from '@electron/image-workspace/works-square-workspace'; import { DesignWorkspaceModuleError } from '@electron/image-workspace/module'; import { getValidWorksSquareAccessToken } from '@electron/services/works-square-session'; import { designValuesFixture } from '../fixtures/design-workspace-v2'; vi.mock('@electron/services/works-square-session', () => ({ getValidWorksSquareAccessToken: vi.fn(), })); const getTokenMock = vi.mocked(getValidWorksSquareAccessToken); function jsonResponse(payload: unknown, status = 200): Response { return new Response(JSON.stringify(payload), { status, headers: { 'Content-Type': 'application/json' }, }); } const serverSummary = { workspace_id: 'workspace-1', client_workspace_id: 'client-workspace-1', title: '咖啡机新品主视觉', direction_id: 'direction-1', session_id: 'session-1', direction_revision: 4, specification_revision: 3, workspace_view_revision: 6, updated_at: '2026-08-30T10:00:00.000Z', }; const serverForm = { workspace_id: 'workspace-1', direction_id: 'direction-1', direction_revision: 4, raw_turn_sequence: 1, specification_revision: 3, workspace_view_revision: 6, specification_revision_id: 'specification-revision-3', specification_digest: 'a'.repeat(64), specification: { schema_version: 1, values: designValuesFixture, field_decisions: {}, }, decision_prompts: [], active_quotes: [], recent_change_set: { interaction_id: 'interaction-1', changes: [] }, }; const serverWorkspace = { workspace: serverSummary, form: serverForm, turns: [{ turn_id: 'turn-1', raw_turn_sequence: 1, user_message: '做一张新品主视觉', assistant_message: '已写入设计表单。', }], tasks: [], assets: [], }; type MockSocket = { readyState: number; onopen: (() => void) | null; onmessage: ((event: { data: unknown }) => void) | null; onerror: ((event: unknown) => void) | null; onclose: ((event: { code: number; reason: string }) => void) | null; send: ReturnType; close: ReturnType; }; function createSocket(): MockSocket { const socket: MockSocket = { readyState: 1, onopen: null, onmessage: null, onerror: null, onclose: null, send: vi.fn(), close: vi.fn((code = 1000, reason = '') => socket.onclose?.({ code, reason })), }; return socket; } describe('Works Square V2 Design Workspace adapter', () => { beforeEach(() => { vi.clearAllMocks(); getTokenMock.mockResolvedValue('access-token'); }); it('maps the V2 bootstrap and canonical Living Form detail', async () => { const fetchMock = vi.fn(async (input: string | URL) => { const url = String(input); if (url.endsWith('/api/design/capabilities')) { return jsonResponse({ conversation: true, generation: true, image: true, video: true }); } if (url.includes('/api/design/workspaces?')) return jsonResponse([serverSummary]); if (url.endsWith('/api/design/workspaces/workspace-1')) return jsonResponse(serverWorkspace); throw new Error(`Unexpected URL: ${url}`); }); const module = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://works.example', fetchImpl: fetchMock as unknown as typeof fetch, }); await expect(module.bootstrap()).resolves.toMatchObject({ workspaces: [{ workspaceId: 'workspace-1', sessionId: 'session-1', directionRevision: 4, specificationRevision: 3, }], }); await expect(module.getWorkspace('workspace-1')).resolves.toMatchObject({ workspace: { title: '咖啡机新品主视觉' }, form: { workspaceId: 'workspace-1', specification: { values: { output: { aspect_ratio: '3:4' } } }, }, turns: [{ userMessage: '做一张新品主视觉' }], }); }); it.each([ { command: { kind: 'apply_input' as const, workspaceId: 'workspace-1', sessionId: 'session-1', expectedDirectionRevision: 4, clientOperationId: 'operation-edit-1', input: { kind: 'direct_edit' as const, operations: [{ kind: 'set' as const, path: 'output.aspect_ratio', value: '16:9' }], }, }, expected: { client_command_id: 'operation-edit-1', name: 'design.input.apply', input: { expected_direction_revision: 4, client_operation_id: 'operation-edit-1', input: { kind: 'direct_edit', operations: [{ kind: 'set', path: 'output.aspect_ratio', value: '16:9' }], }, }, }, }, { command: { kind: 'request_quote' as const, workspaceId: 'workspace-1', sessionId: 'session-1', expectedDirectionRevision: 4, specificationRevision: 3, clientOperationId: 'operation-quote-1', }, expected: { client_command_id: 'operation-quote-1', name: 'design.quote.request', input: { expected_direction_revision: 4, specification_revision: 3, client_operation_id: 'operation-quote-1', }, }, }, { command: { kind: 'confirm_generation' as const, workspaceId: 'workspace-1', sessionId: 'session-1', quoteId: 'quote-1', expectedDirectionRevision: 4, clientOperationId: 'operation-confirm-1', }, expected: { client_command_id: 'operation-confirm-1', name: 'design.generation.confirm', input: { quote_id: 'quote-1', expected_direction_revision: 4, client_operation_id: 'operation-confirm-1', }, }, }, ])('submits $expected.name with one stable operation identity', async ({ command, expected }) => { const requests: Array<{ url: string; init?: RequestInit }> = []; const fetchMock = vi.fn(async (input: string | URL, init?: RequestInit) => { const url = String(input); requests.push({ url, init }); if (url.endsWith('/commands')) { return jsonResponse({ run_id: 'run-1', status: 'queued', error: null }); } if (url.endsWith('/runs/run-1')) { return jsonResponse({ run_id: 'run-1', status: 'succeeded', error: null }); } if (url.endsWith('/api/design/workspaces/workspace-1')) return jsonResponse(serverWorkspace); throw new Error(`Unexpected URL: ${url}`); }); const module = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://works.example', fetchImpl: fetchMock as unknown as typeof fetch, }); const result = await module.submitCommand(command); expect(result).toMatchObject({ clientOperationId: command.clientOperationId, runId: 'run-1', workspace: { workspace: { workspaceId: 'workspace-1' } }, }); const submitted = requests.find((request) => request.url.endsWith('/commands')); expect(JSON.parse(String(submitted?.init?.body))).toEqual(expected); }); it('surfaces a terminal Gateway failure without fetching a false-success Workspace', async () => { const fetchMock = vi.fn(async (input: string | URL) => { const url = String(input); if (url.endsWith('/commands')) { return jsonResponse({ run_id: 'run-1', status: 'queued', error: null }); } if (url.endsWith('/runs/run-1')) { return jsonResponse({ run_id: 'run-1', status: 'failed', error: { code: 'design_quote_blocked', message: 'blocked', retryable: false }, }); } throw new Error(`Unexpected URL: ${url}`); }); const module = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://works.example', fetchImpl: fetchMock as unknown as typeof fetch, }); await expect(module.submitCommand({ kind: 'request_quote', workspaceId: 'workspace-1', sessionId: 'session-1', expectedDirectionRevision: 4, specificationRevision: 3, clientOperationId: 'operation-quote-1', })).rejects.toMatchObject({ status: 422, code: 'design_quote_blocked', } satisfies Partial); expect(fetchMock).not.toHaveBeenCalledWith( expect.stringContaining('/api/design/workspaces/workspace-1'), expect.anything(), ); }); it('resumes the V2 event stream from the supplied sequence and normalizes deltas', async () => { const socket = createSocket(); let streamUrl = ''; const fetchMock = vi.fn(async (input: string | URL) => { const url = String(input); if (url.endsWith('/stream-tickets')) { return jsonResponse({ stream_url: 'https://works.example/api/agents/stream/ticket-1' }); } throw new Error(`Unexpected URL: ${url}`); }); const module = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://works.example', fetchImpl: fetchMock as unknown as typeof fetch, webSocketFactory: async (url) => { streamUrl = url; setTimeout(() => socket.onopen?.(), 0); return { socket }; }, }); const subscription = await module.openWorkspaceEvents({ workspaceId: 'workspace-1', sessionId: 'session-1', afterEventId: 'session-1:7', }); const nextEvent = subscription.events[Symbol.asyncIterator]().next(); socket.onmessage?.({ data: JSON.stringify({ type: 'event', event: { session_id: 'session-1', sequence: 8, runtime: 'design', type: 'design.assistant.delta', schema_version: 1, payload: { workspace_id: 'workspace-1', direction_id: 'direction-1', client_operation_id: 'operation-chat-1', direction_revision: 4, chunk_index: 0, delta: '正在整理设计方向', }, }, }), }); await expect(nextEvent).resolves.toEqual({ done: false, value: { id: 'session-1:8', type: 'design.assistant.delta', workspaceId: 'workspace-1', directionId: 'direction-1', clientOperationId: 'operation-chat-1', directionRevision: 4, chunkIndex: 0, delta: '正在整理设计方向', }, }); expect(streamUrl).toContain('after_sequence=7'); expect(streamUrl).toMatch(/^wss:/); subscription.close(); }); it('refreshes authentication once after a 401', async () => { getTokenMock.mockResolvedValueOnce('expired-token').mockResolvedValueOnce('fresh-token'); const fetchMock = vi.fn(async (_input: string | URL, init?: RequestInit) => { const authorization = new Headers(init?.headers).get('Authorization'); if (authorization === 'Bearer expired-token') return jsonResponse({ detail: 'expired' }, 401); return jsonResponse(serverWorkspace); }); const module = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://works.example', fetchImpl: fetchMock as unknown as typeof fetch, }); await expect(module.getWorkspace('workspace-1')).resolves.toMatchObject({ workspace: { workspaceId: 'workspace-1' }, }); expect(getTokenMock).toHaveBeenNthCalledWith(2, expect.objectContaining({ forceRefresh: true })); }); });