import path from 'path'; import { chmod, mkdir, readFile, writeFile } from 'fs/promises'; import { setTimeout as delay } from 'timers/promises'; import AdmZip from 'adm-zip'; import { describe, expect, test } from 'vitest'; import { buildCliWithEnv, createTempHome, repoRoot, runCli, writeAuthConfig, } from '../helpers/cliRunner'; import { startLocalHttpServer } from '../helpers/localHttpServer'; function outputOf(result: { stdout: string; stderr: string }): string { return `${result.stdout}\n${result.stderr}`; } function createTemplateZipBuffer(): Buffer { const zip = new AdmZip(); const root = 'pinme-worker-template-main'; zip.addFile( `${root}/package.json`, Buffer.from('{"scripts":{"build":"echo build"}}\n'), ); zip.addFile( `${root}/pinme.toml`, Buffer.from('project_name = "template-project"\n'), ); zip.addFile( `${root}/backend/wrangler.toml`, Buffer.from('name = "template-project"\n'), ); zip.addFile( `${root}/dist-worker/worker.js`, Buffer.from('export default { fetch() { return new Response("ok"); } };\n'), ); zip.addFile( `${root}/db/001_init.sql`, Buffer.from('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY);\n'), ); zip.addFile( `${root}/frontend/.env.example`, Buffer.from('VITE_API_URL=https://your-project.example\n'), ); zip.addFile( `${root}/frontend/src/utils/config.ts`, Buffer.from('export const existing = true;\n'), ); zip.addFile( `${root}/frontend/dist/index.html`, Buffer.from( '\n', ), ); return zip.toBuffer(); } async function createFakeNpmBin(root: string): Promise { const binDir = path.join(root, 'fake-bin'); const npmPath = path.join(binDir, 'npm'); await mkdir(binDir, { recursive: true }); await writeFile(npmPath, '#!/bin/sh\nexit 0\n'); await chmod(npmPath, 0o755); return binDir; } describe('pinme CLI success paths with local APIs', () => { test('upload posts chunk workflow and prints public and management URLs', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; const server = await startLocalHttpServer((request, response) => { const bodyText = request.body.toString('utf8'); if (request.method === 'POST' && request.url === '/chunk/init') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(bodyText)).toMatchObject({ file_name: 'index.html', is_directory: false, uid: '0x1234567890abcdef', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { session_id: 'session-1', total_chunks: 1, chunk_size: 1024 * 1024, }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/upload') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(bodyText).toContain('session-1'); expect(bodyText).toContain('0x1234567890abcdef'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { chunk_index: 0, chunk_size: request.body.length }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/complete') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(bodyText)).toMatchObject({ session_id: 'session-1', uid: '0x1234567890abcdef', action: 'upload', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { trace_id: 'trace-1' }, }), ); return; } if ( request.method === 'GET' && request.url === '/up_status?trace_id=trace-1&uid=0x1234567890abcdef' ) { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { is_ready: true, upload_rst: { Bytes: 32, Name: 'index.html', Size: 32, Hash: 'bafy-success', ShortUrl: 'short-success', }, }, }), ); return; } if (request.method === 'GET' && request.url === '/root_domain') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { domain: 'pinme.test' }, }), ); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { bundle = await buildCliWithEnv({ IPFS_API_URL: server.baseUrl, PINME_API_BASE: server.baseUrl, POLL_INTERVAL_SECONDS: '0', MAX_POLL_TIME_MINUTES: '1', }); await writeAuthConfig(temp.home); const result = await runCli( ['upload', path.join(repoRoot, 'test', 'fixtures', 'site', 'index.html')], { home: temp.home, cliPath: bundle.cliPath, timeout: 20000, }, ); expect(result.exitCode, outputOf(result)).toBe(0); expect(outputOf(result)).toContain('URL'); expect(outputOf(result)).toContain('https://short-success.pinme.test'); expect(outputOf(result)).toContain('Management URL'); expect(outputOf(result)).toContain( 'https://preview.pinme.test/#/preview/bafy-success', ); expect(server.requests.map((request) => request.url)).toEqual([ '/chunk/init', '/chunk/upload', '/chunk/complete', '/up_status?trace_id=trace-1&uid=0x1234567890abcdef', '/root_domain', ]); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); test('upload limits concurrent chunk uploads to five', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; let activeUploads = 0; let maxActiveUploads = 0; const server = await startLocalHttpServer(async (request, response) => { const bodyText = request.body.toString('utf8'); if (request.method === 'POST' && request.url === '/chunk/init') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { session_id: 'limited-session-1', total_chunks: 8, chunk_size: 1, }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/upload') { expect(bodyText).toContain('limited-session-1'); activeUploads++; maxActiveUploads = Math.max(maxActiveUploads, activeUploads); await delay(50); activeUploads--; response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { chunk_index: 0, chunk_size: request.body.length }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/complete') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { trace_id: 'limited-trace-1' }, }), ); return; } if ( request.method === 'GET' && request.url === '/up_status?trace_id=limited-trace-1&uid=0x1234567890abcdef' ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { is_ready: true, upload_rst: { Bytes: 8, Name: 'multi-chunk.txt', Size: 8, Hash: 'bafy-limited-success', }, }, }), ); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { const filePath = path.join(temp.home, 'multi-chunk.txt'); await writeFile(filePath, '12345678'); bundle = await buildCliWithEnv({ IPFS_API_URL: server.baseUrl, PINME_API_BASE: server.baseUrl, POLL_INTERVAL_SECONDS: '0', MAX_POLL_TIME_MINUTES: '1', }); await writeAuthConfig(temp.home); const result = await runCli(['upload', filePath], { home: temp.home, cliPath: bundle.cliPath, timeout: 20000, }); expect(result.exitCode, outputOf(result)).toBe(0); expect(maxActiveUploads).toBeLessThanOrEqual(5); expect( server.requests.filter((request) => request.url === '/chunk/upload'), ).toHaveLength(8); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); test('bind uploads content and binds a PinMe subdomain', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; const server = await startLocalHttpServer((request, response) => { const bodyText = request.body.toString('utf8'); if (request.method === 'GET' && request.url === '/pay/wallet/balance') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, msg: 'ok', data: { wallet_balance_usd: 10 }, }), ); return; } if (request.method === 'POST' && request.url === '/check_domain') { expect(JSON.parse(bodyText)).toEqual({ domain_name: 'demo-bind' }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ data: { is_valid: true } })); return; } if (request.method === 'POST' && request.url === '/chunk/init') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(bodyText)).toMatchObject({ file_name: 'index.html', is_directory: false, uid: '0x1234567890abcdef', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { session_id: 'bind-session-1', total_chunks: 1, chunk_size: 1024 * 1024, }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/upload') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(bodyText).toContain('bind-session-1'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { chunk_index: 0, chunk_size: request.body.length }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/complete') { expect(JSON.parse(bodyText)).toMatchObject({ session_id: 'bind-session-1', uid: '0x1234567890abcdef', action: 'bind', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { trace_id: 'bind-trace-1' }, }), ); return; } if ( request.method === 'GET' && request.url === '/up_status?trace_id=bind-trace-1&uid=0x1234567890abcdef' ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { is_ready: true, upload_rst: { Bytes: 32, Name: 'index.html', Size: 32, Hash: 'bafy-bind-success', }, }, }), ); return; } if (request.method === 'POST' && request.url === '/bind_pinme_domain') { expect(JSON.parse(bodyText)).toEqual({ domain_name: 'demo-bind', hash: 'bafy-bind-success', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 200, msg: 'ok' })); return; } if (request.method === 'GET' && request.url === '/root_domain') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { domain: 'pinme.test' }, }), ); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { bundle = await buildCliWithEnv({ IPFS_API_URL: server.baseUrl, PINME_API_BASE: server.baseUrl, POLL_INTERVAL_SECONDS: '0', MAX_POLL_TIME_MINUTES: '1', }); await writeAuthConfig(temp.home); const result = await runCli( [ 'bind', path.join(repoRoot, 'test', 'fixtures', 'site', 'index.html'), '--domain', 'demo-bind', ], { home: temp.home, cliPath: bundle.cliPath, timeout: 20000, }, ); expect(result.exitCode, outputOf(result)).toBe(0); const output = outputOf(result); expect(output).toContain('Wallet balance available: $10.00'); expect(output).toContain('Domain available: demo-bind'); expect(output).toContain('Upload success, CID: bafy-bind-success'); expect(output).toContain('Bind success: demo-bind'); expect(output).toContain('Visit: https://demo-bind.pinme.test'); expect(server.requests.map((request) => request.url)).toEqual([ '/pay/wallet/balance', '/check_domain', '/chunk/init', '/chunk/upload', '/chunk/complete', '/up_status?trace_id=bind-trace-1&uid=0x1234567890abcdef', '/bind_pinme_domain', '/root_domain', ]); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); test('delete --force posts project deletion and prints success', async () => { const temp = await createTempHome(); const server = await startLocalHttpServer((request, response) => { expect(request.method).toBe('POST'); expect(request.url).toBe('/delete_project'); expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(request.body.toString('utf8'))).toEqual({ project_name: 'demo-project', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { project_name: 'demo-project', domain_deleted: true, worker_deleted: true, database_deleted: true, }, }), ); }); try { const bundle = await buildCliWithEnv({ PINME_API_BASE: server.baseUrl, }); await writeAuthConfig(temp.home); const result = await runCli( ['delete', 'demo-project', '--force'], { home: temp.home, cliPath: bundle.cliPath, }, ); expect(result.exitCode, outputOf(result)).toBe(0); expect(outputOf(result)).toContain('Project deleted successfully'); expect(server.requests).toHaveLength(1); await bundle.cleanup(); } finally { await server.close(); await temp.cleanup(); } }); test('update-db uploads SQL files and prints completion', async () => { const temp = await createTempHome(); const server = await startLocalHttpServer((request, response) => { expect(request.method).toBe('POST'); expect(request.url).toBe('/update_db?project_name=fixture-project'); expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(request.body.toString('utf8')).toContain( 'CREATE TABLE IF NOT EXISTS notes', ); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { results: [ { filename: '001_init.sql', status: 'complete', num_queries: 1, duration: 3, changes: 0, rows_read: 0, rows_written: 0, }, ], }, }), ); }); try { const bundle = await buildCliWithEnv({ PINME_API_BASE: server.baseUrl, }); await writeAuthConfig(temp.home); const result = await runCli(['update-db'], { home: temp.home, cwd: path.join(repoRoot, 'test', 'fixtures', 'project'), cliPath: bundle.cliPath, }); expect(result.exitCode, outputOf(result)).toBe(0); expect(outputOf(result)).toContain('Database update complete.'); expect(server.requests).toHaveLength(1); await bundle.cleanup(); } finally { await server.close(); await temp.cleanup(); } }); test('import uploads CAR content and binds a PinMe subdomain', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; const server = await startLocalHttpServer((request, response) => { const bodyText = request.body.toString('utf8'); if (request.method === 'POST' && request.url === '/check_domain') { expect(JSON.parse(bodyText)).toEqual({ domain_name: 'demo-import' }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ data: { is_valid: true } })); return; } if (request.method === 'POST' && request.url === '/chunk/init') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(bodyText)).toMatchObject({ file_name: 'index.html', is_directory: false, uid: '0x1234567890abcdef', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { session_id: 'import-session-1', total_chunks: 1, chunk_size: 1024 * 1024, }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/upload') { expect(bodyText).toContain('import-session-1'); expect(bodyText).toContain('0x1234567890abcdef'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { chunk_index: 0, chunk_size: request.body.length }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/complete') { expect(JSON.parse(bodyText)).toMatchObject({ session_id: 'import-session-1', uid: '0x1234567890abcdef', action: 'import', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { trace_id: 'import-trace-1' }, }), ); return; } if ( request.method === 'GET' && request.url === '/up_status?trace_id=import-trace-1&uid=0x1234567890abcdef' ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { is_ready: true, upload_rst: { Bytes: 32, Name: 'index.html', Size: 32, Hash: 'bafy-import-success', }, }, }), ); return; } if (request.method === 'POST' && request.url === '/bind_pinme_domain') { expect(JSON.parse(bodyText)).toEqual({ domain_name: 'demo-import', hash: 'bafy-import-success', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 200, msg: 'ok' })); return; } if (request.method === 'GET' && request.url === '/root_domain') { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { domain: 'pinme.test' }, }), ); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { bundle = await buildCliWithEnv({ IPFS_API_URL: server.baseUrl, PINME_API_BASE: server.baseUrl, POLL_INTERVAL_SECONDS: '0', MAX_POLL_TIME_MINUTES: '1', }); await writeAuthConfig(temp.home); const result = await runCli( [ 'import', path.join(repoRoot, 'test', 'fixtures', 'site', 'index.html'), '--domain', 'demo-import', ], { home: temp.home, cliPath: bundle.cliPath, timeout: 20000, }, ); expect(result.exitCode, outputOf(result)).toBe(0); const output = outputOf(result); expect(output).toContain('Domain available: demo-import'); expect(output).toContain('URL'); expect(output).toContain('bafy-import-success'); expect(output).toContain('Bind success: demo-import'); expect(output).toContain( 'Visit (Pinme subdomain example): https://demo-import.pinme.test', ); expect(server.requests.map((request) => request.url)).toEqual([ '/check_domain', '/chunk/init', '/chunk/upload', '/chunk/complete', '/up_status?trace_id=import-trace-1&uid=0x1234567890abcdef', '/bind_pinme_domain', '/root_domain', ]); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); test('export requests CAR generation and downloads the completed file', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; const cid = 'bafyexportsuccess'; const outputDir = path.join(temp.home, 'exports'); const server = await startLocalHttpServer((request, response) => { if ( request.method === 'POST' && request.url === `/car/export?cid=${cid}&uid=0x1234567890abcdef` ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, msg: 'ok', data: { cid, status: 'processing', task_id: 'export-task-1', }, }), ); return; } if ( request.method === 'GET' && request.url === '/car/export/status?task_id=export-task-1' ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, msg: 'ok', data: { task_id: 'export-task-1', cid, status: 'completed', download_url: `${server.baseUrl}/downloads/${cid}.car`, }, }), ); return; } if (request.method === 'GET' && request.url === `/downloads/${cid}.car`) { const body = Buffer.from('fixture car payload'); response.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': String(body.length), }); response.end(body); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { bundle = await buildCliWithEnv({ CAR_API_BASE: server.baseUrl, }); await writeAuthConfig(temp.home); const result = await runCli( ['export', cid, '--output', outputDir], { home: temp.home, cliPath: bundle.cliPath, timeout: 20000, }, ); expect(result.exitCode, outputOf(result)).toBe(0); const output = outputOf(result); expect(output).toContain('Export task created: export-task-1'); expect(output).toContain('Export successful'); expect(output).toContain(`CID: ${cid}`); await expect(readFile(path.join(outputDir, `${cid}.car`), 'utf8')) .resolves.toBe('fixture car payload'); expect(server.requests.map((request) => request.url)).toEqual([ `/car/export?cid=${cid}&uid=0x1234567890abcdef`, '/car/export/status?task_id=export-task-1', `/downloads/${cid}.car`, ]); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); test('create scaffolds a project, deploys worker, and uploads frontend dist', async () => { const temp = await createTempHome(); let bundle: Awaited> | undefined; const templateZip = createTemplateZipBuffer(); const server = await startLocalHttpServer((request, response) => { const bodyText = request.body.toString('utf8'); if (request.method === 'POST' && request.url === '/create_worker') { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(JSON.parse(bodyText)).toEqual({ project_name: 'demo-create', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { api_domain: 'https://api.demo-create.pinme.test', metadata: { project_name: 'demo-create', bindings: [ { name: 'API_KEY', text: 'real-api-key' }, { name: 'PROJECT_NAME', text: 'demo-create' }, ], }, project_name: 'demo-create', uuid: 'worker-uuid-1', api_key: 'real-api-key', public_client_config: { auth_api_key: 'public-auth-key', auth_domain: 'auth.pinme.test', auth_project_id: 'pinme-project', tenant_id: 'tenant-1', }, }, }), ); return; } if (request.method === 'GET' && request.url === '/template.zip') { response.writeHead(200, { 'Content-Type': 'application/zip', 'Content-Length': String(templateZip.length), }); response.end(templateZip); return; } if ( request.method === 'PUT' && request.url === '/save_worker?project_name=demo-create' ) { expect(request.headers['token-address']).toBe('0x1234567890abcdef'); expect(request.headers['authentication-tokens']).toBe('test-token'); expect(bodyText).toContain('metadata.json'); expect(bodyText).toContain('worker.js'); expect(bodyText).toContain('001_init.sql'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { sql_results: [ { filename: '001_init.sql', status: 'complete' }, ], }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/init') { expect(JSON.parse(bodyText)).toMatchObject({ is_directory: true, uid: '0x1234567890abcdef', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { session_id: 'create-session-1', total_chunks: 1, chunk_size: 1024 * 1024, }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/upload') { expect(bodyText).toContain('create-session-1'); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { chunk_index: 0, chunk_size: request.body.length }, }), ); return; } if (request.method === 'POST' && request.url === '/chunk/complete') { expect(JSON.parse(bodyText)).toMatchObject({ session_id: 'create-session-1', uid: '0x1234567890abcdef', action: 'project_create', project_name: 'demo-create', }); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { trace_id: 'create-trace-1' }, }), ); return; } if ( request.method === 'GET' && request.url === '/up_status?trace_id=create-trace-1&uid=0x1234567890abcdef&project_name=demo-create' ) { response.writeHead(200, { 'Content-Type': 'application/json' }); response.end( JSON.stringify({ code: 200, data: { is_ready: true, upload_rst: { Bytes: 64, Name: 'dist', Size: 64, Hash: 'bafy-create-frontend', }, }, }), ); return; } response.writeHead(404, { 'Content-Type': 'application/json' }); response.end(JSON.stringify({ code: 404, msg: 'unexpected request' })); }); try { const fakeBin = await createFakeNpmBin(temp.home); bundle = await buildCliWithEnv({ PINME_API_BASE: server.baseUrl, IPFS_API_URL: server.baseUrl, PINME_TEMPLATE_ZIP_URL: `${server.baseUrl}/template.zip`, POLL_INTERVAL_SECONDS: '0', MAX_POLL_TIME_MINUTES: '1', }); await writeAuthConfig(temp.home); const result = await runCli(['create', 'Demo-Create', '--force'], { home: temp.home, cwd: temp.home, cliPath: bundle.cliPath, timeout: 25000, env: { PATH: `${fakeBin}:${process.env.PATH || ''}`, }, }); expect(result.exitCode, outputOf(result)).toBe(0); const output = outputOf(result); expect(output).toContain('Project created successfully.'); expect(output).toContain('Worker deployed'); expect(output).toContain('Frontend URL'); expect(output).toContain('Project Management URL'); const projectDir = path.join(temp.home, 'Demo-Create'); await expect(readFile(path.join(projectDir, 'pinme.toml'), 'utf8')) .resolves.toContain('project_name = "demo-create"'); await expect(readFile(path.join(projectDir, 'pinme.toml'), 'utf8')) .resolves.toContain( 'api_url = "https://api.demo-create.pinme.test"', ); await expect(readFile(path.join(projectDir, 'pinme.toml'), 'utf8')) .resolves.toContain( 'frontend_url = "https://project.pinme.test/demo-create"', ); await expect( readFile(path.join(projectDir, 'backend', 'metadata.json'), 'utf8'), ).resolves.toContain('"PROJECT_NAME"'); await expect( readFile( path.join(projectDir, 'frontend', 'src', 'utils', 'config.ts'), 'utf8', ), ).resolves.toContain('public_client_config'); await expect( readFile(path.join(projectDir, 'frontend', 'dist', 'index.html'), 'utf8'), ).resolves.toContain('https://api.demo-create.pinme.test'); expect(server.requests.map((request) => request.url)).toEqual([ '/create_worker', '/template.zip', '/save_worker?project_name=demo-create', '/chunk/init', '/chunk/upload', '/chunk/complete', '/up_status?trace_id=create-trace-1&uid=0x1234567890abcdef&project_name=demo-create', ]); } finally { if (bundle) { await bundle.cleanup(); } await server.close(); await temp.cleanup(); } }); });