import { beforeEach, describe, expect, it, vi } from 'vitest'; import { AppError } from '@/lib/error-model'; import { hostApiFetch } from '@/lib/host-api'; import { createImageWorkspaceProject, fetchImageWorkspace, ImageWorkspaceApiError, sendImageWorkspaceMessage, uploadImageWorkspaceReference, } from '@/lib/image-workspace'; vi.mock('@/lib/host-api', () => ({ hostApiFetch: vi.fn() })); const hostApiFetchMock = vi.mocked(hostApiFetch); const workspace = { capabilities: { modes: [], models: [], aspectRatios: [], resolutions: [], outputCounts: [], maxReferenceImages: 0, referenceUpload: { enabled: false, acceptedMimeTypes: [] }, }, projects: [], }; describe('image workspace renderer API boundary', () => { beforeEach(() => { hostApiFetchMock.mockReset(); hostApiFetchMock.mockResolvedValue({ success: true, workspace }); }); it('uses a dedicated Main-owned route and forwards the cloud access token', async () => { await fetchImageWorkspace('cloud-token'); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/image-workspace', { headers: { 'X-NianCode-Access-Token': 'cloud-token' }, }); }); it('allows an adapter-backed initial load without an access token', async () => { await fetchImageWorkspace(null); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/image-workspace', {}); }); it('creates a cloud project with only its trimmed name', async () => { await createImageWorkspaceProject('cloud-token', ' 角色设计 '); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/image-workspace/projects', { method: 'POST', headers: { 'X-NianCode-Access-Token': 'cloud-token' }, body: JSON.stringify({ name: '角色设计' }), }); }); it('sends explicit references and server option ids without local task semantics', async () => { await sendImageWorkspaceMessage('cloud-token', { projectId: 'project/one', agentId: 'agent-one', prompt: '继续编辑', referenceImageIds: ['image-one'], settings: { modelId: 'server-model' }, }); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/works/image-workspace/projects/project%2Fone/messages', { method: 'POST', headers: { 'X-NianCode-Access-Token': 'cloud-token' }, body: JSON.stringify({ projectId: 'project/one', agentId: 'agent-one', prompt: '继续编辑', referenceImageIds: ['image-one'], settings: { modelId: 'server-model' }, }), }); }); it('requires the upload response to return a cloud reference id', async () => { hostApiFetchMock.mockResolvedValueOnce({ success: true, workspace, reference: { id: 'reference-one', url: 'https://example.com/reference.png' }, }); await expect(uploadImageWorkspaceReference('cloud-token', { projectId: 'project-one', fileName: 'reference.png', mimeType: 'image/png', contentBase64: 'aW1hZ2U=', })).resolves.toMatchObject({ reference: { id: 'reference-one' } }); }); it('maps the unimplemented Main route to a stable unavailable error', async () => { hostApiFetchMock.mockRejectedValueOnce(new AppError( 'UNKNOWN', '创作空间暂不可用', undefined, { status: 501 }, )); await expect(fetchImageWorkspace('cloud-token')).rejects.toMatchObject({ status: 501, code: 'IMAGE_WORKSPACE_UNAVAILABLE', message: '创作空间暂不可用', }); }); });