import { EventEmitter } from 'node:events'; import type { IncomingMessage, ServerResponse } from 'http'; import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { handleWorksRoutes } from '@electron/api/routes/works'; const readWorksPublishFileMock = vi.hoisted(() => vi.fn()); const readWorksDeployCheckMock = vi.hoisted(() => vi.fn()); vi.mock('@electron/opencode/works-publish-file', () => ({ readWorksPublishFile: (...args: unknown[]) => readWorksPublishFileMock(...args), })); vi.mock('@electron/opencode/works-square-deploy-check', () => ({ readWorksDeployCheck: (...args: unknown[]) => readWorksDeployCheckMock(...args), })); 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 unknown, }; } function createRequest(method: string, body?: unknown, headers: Record = {}): IncomingMessage { const req = new EventEmitter(); Object.assign(req, { method, headers: body === undefined ? headers : { 'content-type': 'application/json', ...headers }, [Symbol.asyncIterator]: async function* () { if (body !== undefined) { yield Buffer.from(JSON.stringify(body)); } }, }); return req as IncomingMessage; } describe('works square host api routes', () => { let tempDir: string | null = null; beforeEach(() => { vi.restoreAllMocks(); readWorksPublishFileMock.mockReset(); readWorksDeployCheckMock.mockReset(); }); afterEach(async () => { if (tempDir) { await rm(tempDir, { recursive: true, force: true }); tempDir = null; } }); it('lists public projects through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ items: [ { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', updated_at: '2026-06-20T22:55:37.790408+08:00', }, ], next_cursor: null, limit: 24, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/works/projects?q=space&category=game&limit=24'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, page: { items: [ { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', updated_at: '2026-06-20T22:55:37.790408+08:00', }, ], next_cursor: null, limit: 24, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects?q=space&category=game&limit=24', { method: 'GET' }, ); }); it('lists public assets through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ items: [ { slug: 'tiny-farm', title: 'Tiny Farm', summary: 'Farm pixel resource package', category: '2D', source: 'Kenney', tags: ['farm', 'pixel'], preview_url: 'https://example.com/tiny-farm.png', image_urls: ['https://example.com/tiny-farm.png'], archive_size_bytes: 204800, extracted_file_count: 141, published_at: '2026-07-09T00:00:00Z', updated_at: '2026-07-09T08:30:00Z', }, ], next_cursor: null, limit: 12, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/works/assets?q=farm&category=2D&tag=pixel&source=Kenney&limit=12'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, assets: { items: [ { slug: 'tiny-farm', title: 'Tiny Farm', summary: 'Farm pixel resource package', category: '2D', source: 'Kenney', tags: ['farm', 'pixel'], preview_url: 'https://example.com/tiny-farm.png', image_urls: ['https://example.com/tiny-farm.png'], archive_size_bytes: 204800, extracted_file_count: 141, published_at: '2026-07-09T00:00:00Z', updated_at: '2026-07-09T08:30:00Z', }, ], next_cursor: null, limit: 12, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/assets?q=farm&category=2D&tag=pixel&source=Kenney&limit=12', { method: 'GET' }, ); }); it('loads a public asset detail through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ slug: 'tiny-farm', title: 'Tiny Farm', summary: 'Farm pixel resource package', category: '2D', source: 'Kenney', tags: ['farm'], preview_url: null, image_urls: [], archive_size_bytes: null, extracted_file_count: null, published_at: null, updated_at: null, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/works/assets/tiny-farm'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, asset: { slug: 'tiny-farm', title: 'Tiny Farm', summary: 'Farm pixel resource package', category: '2D', source: 'Kenney', tags: ['farm'], preview_url: null, image_urls: [], archive_size_bytes: null, extracted_file_count: null, published_at: null, updated_at: null, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/assets/tiny-farm', { method: 'GET' }, ); }); it('downloads a public asset zip into a selected local project', async () => { tempDir = await mkdtemp(join(tmpdir(), 'niancode-works-asset-download-')); const fetchMock = vi.fn() .mockResolvedValueOnce(new Response(null, { status: 307, headers: { Location: 'https://cdn.example.com/tiny-farm.zip', }, })) .mockResolvedValueOnce(new Response(new Uint8Array(Buffer.from('zip bytes')), { status: 200 })); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const listProjects = vi.fn().mockResolvedValue([ { id: 'prj_123', path: tempDir, name: 'Tiny Game', createdAt: '2026-07-09T00:00:00Z', updatedAt: '2026-07-09T00:00:00Z', lastOpenedAt: '2026-07-09T00:00:00Z', }, ]); const handled = await handleWorksRoutes( createRequest('POST', { projectId: 'prj_123' }), response.res, new URL('http://127.0.0.1/api/works/assets/tiny-farm/download'), { opencodeProjectStore: { listProjects } } as never, ); const expectedFilePath = join(tempDir, 'assets', 'resource-square', 'tiny-farm.zip'); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, download: { slug: 'tiny-farm', filePath: expectedFilePath, relativePath: 'assets/resource-square/tiny-farm.zip', bytesWritten: 9, }, }); expect(await readFile(expectedFilePath, 'utf8')).toBe('zip bytes'); expect(fetchMock).toHaveBeenNthCalledWith( 1, 'https://square.nianxx.cn/api/assets/tiny-farm/download', { method: 'GET', redirect: 'manual' }, ); expect(fetchMock).toHaveBeenNthCalledWith( 2, 'https://cdn.example.com/tiny-farm.zip', { method: 'GET', redirect: 'manual' }, ); }); it('creates project metadata with the current SSO access token', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', updated_at: '2026-06-20T22:55:37.790408+08:00', }), { status: 201 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('POST', { accessToken: 'access-token', project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', }, }), response.res, new URL('http://127.0.0.1/api/works/projects'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(201); expect(response.json()).toEqual({ success: true, project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', updated_at: '2026-06-20T22:55:37.790408+08:00', }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects', { method: 'POST', headers: { Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }, body: JSON.stringify({ app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', }), }, ); }); it('lists the current user projects through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ items: [ { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch space trash', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', status: 'draft', updated_at: '2026-06-20T22:55:37.790408+08:00', playable: false, runtime_url: null, }, ], next_cursor: null, limit: 50, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/projects/mine?limit=50'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, page: { items: [ { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch space trash', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', status: 'draft', updated_at: '2026-06-20T22:55:37.790408+08:00', playable: false, runtime_url: null, }, ], next_cursor: null, limit: 50, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects/mine?limit=50', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); it('loads current token plan usage through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ five_hour_remaining_percent: 72.5, weekly_remaining_percent: 48, }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/billing/token-usage'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, usage: { five_hour_remaining_percent: 72.5, weekly_remaining_percent: 48, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/billing/token-usage', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); it('loads the current Agent Profile through the Works Square API', async () => { const profile = { display_name: '小泥', age: 18, gender: 'female', share_age_with_agents: true, share_gender_with_agents: true, analysis_enabled: true, completed: true, version: 2, updated_at: '2026-07-12T00:00:00Z', }; const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify(profile), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/user/agent-profile'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, profile }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/user/agent-profile', { method: 'GET', headers: { Authorization: 'Bearer access-token' }, }, ); }); it('keeps Agent Profile version-conflict detail available to the renderer', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ detail: { code: 'agent_profile_version_conflict', current_version: 4, }, }), { status: 409 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('PUT', { display_name: '小泥', age: null, gender: null, share_age_with_agents: false, share_gender_with_agents: false, analysis_enabled: true, version: 3, }, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/user/agent-profile'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 409, error: 'Agent Profile request failed (409)', detail: { code: 'agent_profile_version_conflict', current_version: 4, }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/user/agent-profile', { method: 'PUT', headers: { Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }, body: JSON.stringify({ display_name: '小泥', age: null, gender: null, share_age_with_agents: false, share_gender_with_agents: false, analysis_enabled: true, version: 3, }), }, ); }); it('submits an image generation task through the Works Square AI gateway API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ task_id: 'task_1', status: 'queued', model: 'gpt-image-2', result_urls: [], }), { status: 202 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('POST', { prompt: '未来城市里的儿童编程课海报', image_urls: [], size: '1:1', quality: 'medium', resolution: '2K', }, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/ai-gateway/images/generations'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(202); expect(response.json()).toEqual({ success: true, job: { task_id: 'task_1', status: 'queued', model: 'gpt-image-2', result_urls: [], }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/ai-gateway/images/generations', { method: 'POST', headers: { Authorization: 'Bearer access-token', 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: '未来城市里的儿童编程课海报', image_urls: [], size: '1:1', quality: 'medium', resolution: '2K', }), }, ); }); it('loads an image generation task through the Works Square AI gateway API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ task_id: 'task_1', status: 'succeeded', model: 'gpt-image-2', result_urls: ['https://example.com/result.png'], }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/ai-gateway/images/tasks/task_1'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, job: { task_id: 'task_1', status: 'succeeded', model: 'gpt-image-2', result_urls: ['https://example.com/result.png'], }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/ai-gateway/images/tasks/task_1', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); it('loads current user project status with uploaded versions', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ project: { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch space trash', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', status: 'draft', updated_at: '2026-06-20T22:55:37.790408+08:00', playable: false, runtime_url: null, }, latest_version: { id: 'version-new', version_name: 'v1.1.0', review_status: 'building', change_log: 'Add score board', build_job_id: 'job-new', created_at: '2026-06-21T10:30:00+08:00', }, versions: [], }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/projects/mine/space-cleaner/status'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, status: { project: { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch space trash', cover_url: null, category: 'game', age_band: '8-12', difficulty: 'beginner', status: 'draft', updated_at: '2026-06-20T22:55:37.790408+08:00', playable: false, runtime_url: null, }, latest_version: { id: 'version-new', version_name: 'v1.1.0', review_status: 'building', change_log: 'Add score board', build_job_id: 'job-new', created_at: '2026-06-21T10:30:00+08:00', }, versions: [], }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects/mine/space-cleaner/status', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); it.each(['pass', 'warning'] as const)('uploads a zip version when the deployment check is %s', async (deployStatus) => { tempDir = await mkdtemp(join(tmpdir(), 'niancode-works-upload-')); const zipPath = join(tempDir, 'project.zip'); await writeFile(zipPath, Buffer.from('zip bytes')); const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ version_id: 'b2f6f1dd-cf4b-4f03-a4b8-a71e2c8dd5f1', review_status: 'building', }), { status: 201 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; readWorksPublishFileMock.mockResolvedValue({ status: 'ready', filePath: join(tempDir, 'works-publish.json'), publish: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', category: 'game', age_band: '8-12', difficulty: 'beginner', version_name: 'v1.0.0', change_log: 'First submitted version', zip_file_path: zipPath, }, }); readWorksDeployCheckMock.mockResolvedValue({ status: deployStatus, filePath: join(tempDir, 'works-deploy-check.json'), checks: {}, }); const handled = await handleWorksRoutes( createRequest('POST', { accessToken: 'access-token', projectId: project.id, versionName: 'v1.0.0', changeLog: 'First submitted version', zipFilePath: zipPath, }), response.res, new URL('http://127.0.0.1/api/works/projects/space-cleaner/versions/upload'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(201); expect(response.json()).toEqual({ success: true, upload: { version_id: 'b2f6f1dd-cf4b-4f03-a4b8-a71e2c8dd5f1', review_status: 'building', }, }); expect(fetchMock).toHaveBeenCalledOnce(); const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe('https://square.nianxx.cn/api/projects/space-cleaner/versions/upload'); expect(init.method).toBe('POST'); expect(init.headers).toEqual({ Authorization: 'Bearer access-token' }); expect(init.body).toBeInstanceOf(FormData); const form = init.body as FormData; expect(form.get('version_name')).toBe('v1.0.0'); expect(form.get('change_log')).toBe('First submitted version'); const archive = form.get('archive'); expect(archive).toBeInstanceOf(File); expect((archive as File).name).toBe('project.zip'); expect((archive as File).type).toBe('application/zip'); }); it('blocks upload when the deployment check is not PASS', async () => { tempDir = await mkdtemp(join(tmpdir(), 'niancode-works-upload-blocked-')); const zipPath = join(tempDir, 'project.zip'); await writeFile(zipPath, Buffer.from('zip bytes')); const project = { id: 'project-blocked', path: tempDir, name: 'blocked' }; readWorksPublishFileMock.mockResolvedValue({ status: 'ready', filePath: join(tempDir, 'works-publish.json'), publish: { app_id: 'blocked-app', title: '阻断测试', summary: '阻断测试', category: 'web', age_band: '10-12', difficulty: 'beginner', version_name: 'v1.0.0', change_log: '阻断测试', zip_file_path: zipPath, }, }); readWorksDeployCheckMock.mockResolvedValue({ status: 'blocked', filePath: join(tempDir, 'works-deploy-check.json'), error: 'HTTP smoke 失败', }); const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('POST', { accessToken: 'access-token', projectId: project.id, versionName: 'v1.0.0', changeLog: '阻断测试', zipFilePath: zipPath, }), response.res, new URL('http://127.0.0.1/api/works/projects/blocked-app/versions/upload'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(400); expect(response.json()).toEqual({ success: false, error: 'BLOCKED: HTTP smoke 失败' }); expect(fetchMock).not.toHaveBeenCalled(); }); it('transcribes uploaded speech through the Works Square API', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ text: 'open the file', model: 'gpt-4o-mini-transcribe', }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('POST', { accessToken: 'access-token', audioBase64: Buffer.from('wav bytes').toString('base64'), language: 'zh', prompt: 'NianCode command', }), response.res, new URL('http://127.0.0.1/api/works/speech/transcriptions'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, transcription: { text: 'open the file', model: 'gpt-4o-mini-transcribe', }, }); expect(fetchMock).toHaveBeenCalledOnce(); const [url, init] = fetchMock.mock.calls[0] as [string, RequestInit]; expect(url).toBe('https://square.nianxx.cn/api/speech/transcriptions'); expect(init.method).toBe('POST'); expect(init.headers).toEqual({ Authorization: 'Bearer access-token' }); expect(init.body).toBeInstanceOf(FormData); const form = init.body as FormData; expect(form.get('language')).toBe('zh'); expect(form.get('prompt')).toBe('NianCode command'); const audio = form.get('audio'); expect(audio).toBeInstanceOf(File); expect((audio as File).name).toBe('voice.wav'); expect((audio as File).type).toBe('audio/wav'); expect(await (audio as File).text()).toBe('wav bytes'); }); it('lists uploaded versions for a project with the current SSO access token', async () => { const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ items: [ { id: 'ver_1', version_name: 'v1.0.0', review_status: 'building', change_log: 'First submitted version', build_job_id: 'job_1', created_at: '2026-06-21T10:30:00+08:00', }, ], }), { status: 200 }), ); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('GET', undefined, { 'x-niancode-access-token': 'access-token' }), response.res, new URL('http://127.0.0.1/api/works/projects/space-cleaner/versions'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, versions: { items: [ { id: 'ver_1', version_name: 'v1.0.0', review_status: 'building', change_log: 'First submitted version', build_job_id: 'job_1', created_at: '2026-06-21T10:30:00+08:00', }, ], }, }); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects/space-cleaner/versions', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); });