import { EventEmitter } from 'node:events'; import type { IncomingMessage, ServerResponse } from 'http'; import { mkdir, 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'; import { createProjectConfig } from '../../shared/project-config'; const getValidWorksSquareAccessTokenMock = vi.hoisted(() => vi.fn()); vi.mock('@electron/services/works-square-session', () => ({ getValidWorksSquareAccessToken: (...args: unknown[]) => getValidWorksSquareAccessTokenMock(...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; } async function writePublishableProject(projectPath: string): Promise { await mkdir(join(projectPath, 'src'), { recursive: true }); await mkdir(join(projectPath, '.niancode'), { recursive: true }); await writeFile( join(projectPath, '.niancode', 'project.json'), JSON.stringify(createProjectConfig('2026-08-09T00:00:00.000Z', 'mini_game')), 'utf8', ); await writeFile(join(projectPath, 'package.json'), JSON.stringify({ name: 'space-cleaner', private: true, packageManager: 'npm@10.9.2', scripts: { build: 'vite build' }, devDependencies: { vite: '^7.0.0' }, }), 'utf8'); await writeFile(join(projectPath, 'package-lock.json'), JSON.stringify({ name: 'space-cleaner', lockfileVersion: 3, requires: true, packages: {}, }), 'utf8'); await writeFile(join(projectPath, 'index.html'), '
', 'utf8'); await writeFile(join(projectPath, 'src', 'main.ts'), 'console.log("ready")\n', 'utf8'); await writeFile(join(projectPath, 'VERSION.md'), '# Version\n\nCurrent: v2.3.4\n', 'utf8'); } describe('works square host api routes', () => { let tempDir: string | null = null; beforeEach(() => { vi.restoreAllMocks(); getValidWorksSquareAccessTokenMock.mockReset(); getValidWorksSquareAccessTokenMock.mockResolvedValue('main-owned-access-token'); }); 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, play_url: '/apps/space-cleaner/', runtime_url: null, owner_email: 'private@example.com', }, latest_version: { id: 'version-new', version_name: 'v1.1.0', review_status: 'building', change_log: 'Add score board', build_job_id: 'job-new', build_status: 'failed', build_error_code: 'BUILD_COMMAND_FAILED', build_error_message: 'Traceback: C:\\private\\builder.ts token=secret', release_id: null, created_at: '2026-06-21T10:30:00+08:00', internal_trace: 'must not reach renderer', }, versions: [], database_debug: 'must not reach renderer', }), { 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, play_url: '/apps/space-cleaner/', 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', build_status: 'failed', build_error_code: 'BUILD_COMMAND_FAILED', release_id: null, rejection_reason: null, created_at: '2026-06-21T10:30:00+08:00', }, versions: [], }, }); expect(JSON.stringify(response.json())).not.toContain('private@example.com'); expect(JSON.stringify(response.json())).not.toContain('token=secret'); expect(JSON.stringify(response.json())).not.toContain('internal_trace'); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects/mine/space-cleaner/status', { method: 'GET', headers: { Authorization: 'Bearer access-token', }, }, ); }); it('does not expose the retired direct ZIP upload route', async () => { const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const handled = await handleWorksRoutes( createRequest('POST', {}), response.res, new URL('http://127.0.0.1/api/works/projects/space-cleaner/versions/upload'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(404); expect(fetchMock).not.toHaveBeenCalled(); }); it('loads current project status with the Main-owned session when no renderer token is sent', async () => { const status = { project: { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch space trash', }, latest_version: { id: 'version-new', version_name: 'v1.1.0', review_status: 'building', change_log: '通过 Makelore 一键提交', build_job_id: 'job-new', build_status: 'failed', build_error_code: 'BUILD_COMMAND_FAILED', build_error_message: 'npm run build 执行失败', release_id: null, created_at: '2026-08-07T10:30:00+08:00', worker_debug: 'private worker hostname', }, versions: [], }; const fetchMock = vi.fn().mockResolvedValueOnce( new Response(JSON.stringify(status), { 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/mine/space-cleaner/status'), {} as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: true, status: { project: status.project, latest_version: { id: 'version-new', version_name: 'v1.1.0', review_status: 'building', change_log: '通过 Makelore 一键提交', build_job_id: 'job-new', build_status: 'failed', build_error_code: 'BUILD_COMMAND_FAILED', release_id: null, rejection_reason: null, created_at: '2026-08-07T10:30:00+08:00', }, versions: [], }, }); expect(JSON.stringify(response.json())).not.toContain('build_error_message'); expect(JSON.stringify(response.json())).not.toContain('worker_debug'); expect(getValidWorksSquareAccessTokenMock).toHaveBeenCalledOnce(); expect(fetchMock).toHaveBeenCalledWith( 'https://square.nianxx.cn/api/projects/mine/space-cleaner/status', { method: 'GET', headers: { Authorization: 'Bearer main-owned-access-token' }, }, ); }); it('does not expose Main session refresh errors from the current project status route', async () => { getValidWorksSquareAccessTokenMock.mockRejectedValueOnce( new Error('refresh failed at C:\\private\\session.ts token=secret'), ); const response = createResponse(); await handleWorksRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/works/projects/mine/space-cleaner/status'), {} as never, ); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 503, code: 'PROJECT_STATUS_UNAVAILABLE', error: '暂时无法获取作品处理状态,请稍后重试。', }); expect(JSON.stringify(response.json())).not.toContain('token=secret'); }); it('fails safely when a successful status response does not match the allowlisted schema', async () => { vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce( new Response(JSON.stringify({ project: { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch trash' }, latest_version: { build_error_message: 'private traceback token=secret' }, versions: 'not-an-array', internal: 'database coordinates', }), { status: 200 }), )); const response = createResponse(); await handleWorksRoutes( createRequest('GET'), response.res, new URL('http://127.0.0.1/api/works/projects/mine/space-cleaner/status'), {} as never, ); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 502, code: 'PROJECT_STATUS_UNAVAILABLE', error: '暂时无法获取作品处理状态,请稍后重试。', }); expect(JSON.stringify(response.json())).not.toContain('token=secret'); }); it('packages and submits source with Main-owned credentials and automatic release metadata', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-publish-')); await writePublishableProject(tempDir); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const projectMetadata = { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', category: 'game', }; const fetchMock = vi.fn() .mockResolvedValueOnce(new Response(JSON.stringify(projectMetadata), { status: 201 })) .mockResolvedValueOnce(new Response(JSON.stringify({ version_id: 'version-1', review_status: 'building', build_job_id: 'job-1', build_status: 'queued', build_error_message: 'private builder path token=secret', internal_trace: 'private worker hostname', }), { status: 201 })); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); const recordSubmitted = vi.fn(async () => undefined); const handled = await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: projectMetadata }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) }, worksSubmissionBinding: { recordSubmitted }, } as never, ); expect(handled).toBe(true); expect(response.statusCode).toBe(201); expect(response.json()).toMatchObject({ success: true, package: { archiveName: 'project.zip', fileCount: 5, manifest: { schema_version: 1, kind: 'web', runtime: 'static', build: { preset: 'vite', package_manager: 'npm', entry: 'index.html' }, }, }, upload: { version_id: 'version-1', review_status: 'building', }, }); expect(response.json().upload).toEqual({ version_id: 'version-1', review_status: 'building', build_job_id: 'job-1', build_status: 'queued', }); expect(JSON.stringify(response.json())).not.toContain('build_error_message'); expect(JSON.stringify(response.json())).not.toContain('internal_trace'); expect(response.json().package).not.toHaveProperty('archivePath'); expect(getValidWorksSquareAccessTokenMock).toHaveBeenCalledOnce(); expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock.mock.calls[0]).toEqual([ 'https://square.nianxx.cn/api/projects', { method: 'POST', headers: { Authorization: 'Bearer main-owned-access-token', 'Content-Type': 'application/json', }, body: JSON.stringify(projectMetadata), }, ]); const [, uploadInit] = fetchMock.mock.calls[1] as [string, RequestInit]; expect(uploadInit.headers).toMatchObject({ Authorization: 'Bearer main-owned-access-token', 'Idempotency-Key': expect.stringMatching(/^makelore-/), }); const form = uploadInit.body as FormData; expect(form.get('version_name')).toBe('v2.3.4'); expect(form.get('change_log')).toBe('通过 Makelore 一键提交'); const archive = form.get('archive'); expect(archive).toBeInstanceOf(File); expect((archive as File).name).toBe('project.zip'); expect((archive as File).size).toBeGreaterThan(0); expect(recordSubmitted).toHaveBeenCalledWith(project.id, { appId: 'space-cleaner', versionId: 'version-1', versionName: 'v2.3.4', reviewStatus: 'building', zipSha256: expect.stringMatching(/^[a-f0-9]{64}$/), }); }); it('keeps a confirmed submission successful when the local preview mapping cannot be saved', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-mapping-failure-')); await writePublishableProject(tempDir); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const fetchMock = vi.fn() .mockResolvedValueOnce(new Response('{}', { status: 201 })) .mockResolvedValueOnce(new Response(JSON.stringify({ version_id: 'version-1', review_status: 'building', }), { status: 201 })); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', }, }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) }, worksSubmissionBinding: { recordSubmitted: vi.fn(async () => { throw new Error('disk unavailable'); }), }, } as never, ); expect(response.statusCode).toBe(201); expect(response.json()).toMatchObject({ success: true, upload: { version_id: 'version-1', review_status: 'building' }, }); expect(fetchMock).toHaveBeenCalledTimes(2); }); it('uses a bounded timestamp version when VERSION.md is oversized', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-version-limit-')); await writePublishableProject(tempDir); await writeFile(join(tempDir, 'VERSION.md'), Buffer.alloc(65 * 1024, 0x31)); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const fetchMock = vi.fn() .mockResolvedValueOnce(new Response('{}', { status: 201 })) .mockResolvedValueOnce(new Response(JSON.stringify({ version_id: 'version-1', review_status: 'building', }), { status: 201 })); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', }, }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never, ); expect(response.json().success).toBe(true); const uploadInit = fetchMock.mock.calls[1]?.[1] as RequestInit; expect((uploadInit.body as FormData).get('version_name')).toMatch(/^v\d{14}$/); }); it('retries transient status and network failures with stable bytes, then rotates the key per Host request', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-retry-')); await writePublishableProject(tempDir); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const projectMetadata = { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', }; const retryResponse = new Response('sensitive upstream detail', { status: 503 }); const cancelSpy = vi.spyOn(retryResponse.body!, 'cancel'); const uploadPayload = JSON.stringify({ version_id: 'version-1', review_status: 'building' }); const fetchMock = vi.fn() .mockResolvedValueOnce(new Response('{}', { status: 201 })) .mockResolvedValueOnce(retryResponse) .mockResolvedValueOnce(new Response(uploadPayload, { status: 201 })) .mockResolvedValueOnce(new Response('{}', { status: 201 })) .mockRejectedValueOnce(new Error('socket reset with secret upstream detail')) .mockResolvedValueOnce(new Response(uploadPayload, { status: 201 })); vi.stubGlobal('fetch', fetchMock); const ctx = { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never; for (let index = 0; index < 2; index += 1) { const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: projectMetadata }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), ctx, ); expect(response.json().success).toBe(true); } const uploadCalls = fetchMock.mock.calls.filter(([url]) => String(url).endsWith('/versions/upload')) as [string, RequestInit][]; expect(uploadCalls).toHaveLength(4); const keys = uploadCalls.map(([, init]) => (init.headers as Record)['Idempotency-Key']); expect(keys[0]).toBe(keys[1]); expect(keys[2]).toBe(keys[3]); expect(keys[2]).not.toBe(keys[0]); const archiveBytes = await Promise.all(uploadCalls.map(async ([, init]) => { const archive = (init.body as FormData).get('archive') as File; return Buffer.from(await archive.arrayBuffer()); })); expect(archiveBytes[1]).toEqual(archiveBytes[0]); expect(archiveBytes[2]).toEqual(archiveBytes[0]); expect(archiveBytes[3]).toEqual(archiveBytes[0]); expect(cancelSpy).toHaveBeenCalledOnce(); }); it('turns a malformed successful upload response into a safe retryable failure', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-invalid-success-')); await writePublishableProject(tempDir); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const fetchMock = vi.fn() .mockResolvedValueOnce(new Response('{}', { status: 201 })) .mockResolvedValueOnce(new Response(JSON.stringify({ review_status: 'building', build_error_message: 'private traceback token=secret', internal_trace: 'private worker hostname', }), { status: 200 })); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', }, }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never, ); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 502, code: 'WORKS_SQUARE_UNAVAILABLE', error: '发布服务返回结果异常,请稍后重试。', }); expect(JSON.stringify(response.json())).not.toContain('token=secret'); }); it('returns a safe structured error without exposing an upstream response body', async () => { tempDir = await mkdtemp(join(tmpdir(), 'makelore-source-safe-error-')); await writePublishableProject(tempDir); const project = { id: 'project-1', path: tempDir, name: 'space-cleaner' }; const upstream = new Response(JSON.stringify({ detail: 'internal database table and stack trace', token: 'secret-token', }), { status: 422 }); const cancelSpy = vi.spyOn(upstream.body!, 'cancel'); vi.stubGlobal('fetch', vi.fn().mockResolvedValueOnce(upstream)); const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: project.id, project: { app_id: 'space-cleaner', title: '太空清洁队', summary: '收集漂浮垃圾的小游戏。', }, }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn(async () => [project]) } } as never, ); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 422, code: 'PROJECT_CREATE_REJECTED', error: '平台没有接受作品信息,请修改后重试。', }); expect(JSON.stringify(response.json())).not.toContain('database'); expect(JSON.stringify(response.json())).not.toContain('secret-token'); expect(cancelSpy).toHaveBeenCalledOnce(); }); it('fails safely before packaging when the Main session is unavailable', async () => { getValidWorksSquareAccessTokenMock.mockResolvedValueOnce(null); const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); const response = createResponse(); await handleWorksRoutes( createRequest('POST', { projectId: 'project-1', project: { app_id: 'space-cleaner', title: 'Space Cleaner', summary: 'Catch trash' }, }), response.res, new URL('http://127.0.0.1/api/works/projects/publish-source'), { opencodeProjectStore: { listProjects: vi.fn() } } as never, ); expect(response.statusCode).toBe(200); expect(response.json()).toEqual({ success: false, status: 401, code: 'AUTH_REQUIRED', error: '登录状态已失效,请重新登录。', }); 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', }, }, ); }); });