import { beforeEach, describe, expect, it, vi } from 'vitest'; import { WorksSquareDesignWorkspace } from '@electron/image-workspace/works-square-workspace'; import { getValidWorksSquareAccessToken } from '@electron/services/works-square-session'; vi.mock('@electron/services/works-square-session', () => ({ getValidWorksSquareAccessToken: vi.fn(), })); const getTokenMock = vi.mocked(getValidWorksSquareAccessToken); const serverWorkspace = { workspace_id: 'workspace-one', title: '海洋公益海报', turn_revision: 1, view_revision: 2, phase: 'awaiting_confirmation', brief: { version: 1, status: 'ready', medium: 'image', summary: '保护海洋的竖版公益海报', ready: true, missing_decision: null, }, messages: [{ role: 'assistant', kind: 'confirmation', text: '方向已经明确,是否开始生成?', quick_replies: ['确认生成'], generation_quote: { quote_id: 'quote-one', status: 'active', medium: 'image', brief_version: 1, brief_summary: '保护海洋的竖版公益海报', quoted_design_points: 1, expires_at: '2026-07-31T11:00:00Z', }, turn_revision: 1, created_at: '2026-07-31T10:00:00Z', }], updated_at: '2026-07-31T10:00:00Z', }; function jsonResponse(payload: unknown, status = 200): Response { return new Response(JSON.stringify(payload), { status, headers: { 'Content-Type': 'application/json' }, }); } describe('Works Square AI design adapter', () => { beforeEach(() => { getTokenMock.mockReset(); getTokenMock.mockResolvedValue('access-one'); }); it('maps the server snake_case Workspace contract into the shared client model', async () => { const fetchMock = vi.fn() .mockResolvedValueOnce(jsonResponse({ conversation: true, generation: true, image: true, video: false, })) .mockResolvedValueOnce(jsonResponse([serverWorkspace])); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.bootstrap()).resolves.toMatchObject({ capabilities: { conversation: true, image: true, video: false }, workspaces: [{ workspaceId: 'workspace-one', title: '海洋公益海报', turnRevision: 1, viewRevision: 2, brief: { missingDecision: null }, }], }); expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock.mock.calls.every(([, init]) => ( (init?.headers as Record).Authorization === 'Bearer access-one' ))).toBe(true); }); it('keeps task creation behind structured Quote confirmation', async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse(serverWorkspace)); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example/', fetchImpl: fetchMock, }); const workspace = await adapter.confirmGeneration({ workspaceId: 'workspace-one', clientTurnId: 'turn-two', expectedTurnRevision: 1, quoteId: 'quote-one', }); expect(workspace.workspaceId).toBe('workspace-one'); expect(fetchMock).toHaveBeenCalledWith( 'https://square.example/api/design/workspaces/workspace-one/turns', expect.objectContaining({ method: 'POST', body: JSON.stringify({ client_turn_id: 'turn-two', expected_turn_revision: 1, message: '确认生成', attachment_asset_ids: [], action: { type: 'confirm_generation', quote_id: 'quote-one' }, }), }), ); }); it('maps stable generation tasks and private asset relay paths', async () => { const fetchMock = vi.fn().mockResolvedValue(jsonResponse([{ task_id: 'task-one', workspace_id: 'workspace-one', medium: 'image', status: 'succeeded', brief_version: 1, brief_summary: '海洋公益海报', quote_id: 'quote-one', quoted_design_points: 1, failure_code: null, result_assets: [{ asset_id: 'asset-one', media_type: 'image', mime_type: 'image/png', width: 1024, height: 1280, duration_milliseconds: null, created_at: '2026-07-31T10:01:00Z', }], created_at: '2026-07-31T10:00:00Z', updated_at: '2026-07-31T10:01:00Z', }])); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); await expect(adapter.listTasks('workspace-one')).resolves.toMatchObject([{ taskId: 'task-one', status: 'succeeded', resultAssets: [{ assetId: 'asset-one', contentPath: '/api/works/image-workspace/workspaces/workspace-one/assets/asset-one/content', }], }]); }); it('refreshes the Main-owned session once after an upstream 401 and preserves Range', async () => { getTokenMock .mockResolvedValueOnce('expired-token') .mockResolvedValueOnce('fresh-token'); const fetchMock = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 401 })) .mockResolvedValueOnce(new Response('partial', { status: 206, headers: { 'Content-Type': 'video/mp4', 'Content-Range': 'bytes 0-6/100', }, })); const adapter = new WorksSquareDesignWorkspace({ apiBaseUrl: 'https://square.example', fetchImpl: fetchMock, }); const response = await adapter.openAssetContent( 'workspace-one', 'asset-one', 'bytes=0-6', ); expect(response.status).toBe(206); expect(getTokenMock).toHaveBeenNthCalledWith(2, { fetchImpl: fetchMock, forceRefresh: true, }); expect(fetchMock).toHaveBeenNthCalledWith( 2, expect.stringContaining('/assets/asset-one/content'), expect.objectContaining({ headers: { Range: 'bytes=0-6', Authorization: 'Bearer fresh-token', }, }), ); }); });