import type { IncomingMessage, ServerResponse } from 'node:http'; import { Readable } from 'node:stream'; import { describe, expect, it, vi } from 'vitest'; import { handleImageWorkspaceRoutes } from '@electron/api/routes/image-workspace'; import type { HostApiContext } from '@electron/api/context'; import type { ImageWorkspaceSnapshot } from '../../shared/image-workspace'; function createResponse() { const chunks: string[] = []; const res = { statusCode: 0, setHeader: vi.fn(), end: vi.fn((chunk?: string) => { if (chunk) chunks.push(chunk); }), } as unknown as ServerResponse; return { res, get statusCode() { return res.statusCode; }, json: () => JSON.parse(chunks.join('')) as Record, }; } function createRequest(method: string, body?: unknown): IncomingMessage { const request = Readable.from(body === undefined ? [] : [JSON.stringify(body)]) as unknown as IncomingMessage; request.method = method; request.headers = body === undefined ? {} : { 'content-type': 'application/json' }; return request; } const localSnapshot: ImageWorkspaceSnapshot = { capabilities: { modes: [], models: [], aspectRatios: [], resolutions: [], outputCounts: [], maxReferenceImages: 0, referenceUpload: { enabled: false, acceptedMimeTypes: [] }, }, projects: [], activeProjectId: null, }; describe('image workspace Main route boundary', () => { it('does not claim unrelated routes', async () => { const response = createResponse(); const handled = await handleImageWorkspaceRoutes( { method: 'GET' } as IncomingMessage, response.res, new URL('http://127.0.0.1/api/works/projects'), {} as never, ); expect(handled).toBe(false); expect(response.res.end).not.toHaveBeenCalled(); }); it('returns a stable unavailable response without fabricating cloud state', async () => { const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleImageWorkspaceRoutes( { method: 'GET' } as IncomingMessage, response.res, new URL('http://127.0.0.1/api/works/image-workspace'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(501); expect(response.json()).toEqual({ success: false, status: 501, code: 'IMAGE_WORKSPACE_UNAVAILABLE', error: '创作空间暂不可用', }); expect(fetchMock).not.toHaveBeenCalled(); }); it('serves the local workspace only when Main injects the development service', async () => { const imageWorkspace = { getSnapshot: vi.fn().mockResolvedValue(localSnapshot), createProject: vi.fn().mockResolvedValue(localSnapshot), addAgent: vi.fn().mockResolvedValue(localSnapshot), sendMessage: vi.fn().mockResolvedValue(localSnapshot), uploadReference: vi.fn(), reset: vi.fn().mockResolvedValue(localSnapshot), }; const ctx = { imageWorkspace } as unknown as HostApiContext; const getResponse = createResponse(); await handleImageWorkspaceRoutes( createRequest('GET'), getResponse.res, new URL('http://127.0.0.1/api/works/image-workspace'), ctx, ); expect(getResponse.statusCode).toBe(200); expect(getResponse.json()).toMatchObject({ success: true, workspace: localSnapshot }); const createResponseBody = createResponse(); await handleImageWorkspaceRoutes( createRequest('POST', { name: ' 测试项目 ' }), createResponseBody.res, new URL('http://127.0.0.1/api/works/image-workspace/projects'), ctx, ); expect(imageWorkspace.createProject).toHaveBeenCalledWith(' 测试项目 '); const messageResponse = createResponse(); await handleImageWorkspaceRoutes( createRequest('POST', { projectId: 'spoofed-project', agentId: 'agent-one', prompt: '继续编辑', referenceImageIds: ['reference-one'], settings: { outputCountId: '2' }, }), messageResponse.res, new URL('http://127.0.0.1/api/works/image-workspace/projects/project%2Fone/messages'), ctx, ); expect(imageWorkspace.sendMessage).toHaveBeenCalledWith({ projectId: 'project/one', agentId: 'agent-one', prompt: '继续编辑', referenceImageIds: ['reference-one'], settings: { outputCountId: '2' }, }); const resetResponse = createResponse(); await handleImageWorkspaceRoutes( createRequest('DELETE'), resetResponse.res, new URL('http://127.0.0.1/api/works/image-workspace/local-data'), ctx, ); expect(imageWorkspace.reset).toHaveBeenCalledOnce(); expect(resetResponse.json()).toMatchObject({ workspace: { projects: [] } }); }); });