#!/usr/bin/env node /** * Verify publication delivery chain: file layer vs /u/ publication layer. */ import crypto from 'node:crypto'; import fs from 'node:fs/promises'; import path from 'node:path'; const base = process.env.VERIFY_BASE_URL || 'http://127.0.0.1:8081'; const username = process.env.VERIFY_USER || 'john'; const password = process.env.VERIFY_PASSWORD || '888888'; const userId = process.env.VERIFY_USER_ID || '1c99b83b-0454-474f-a5d2-129d34506a32'; const checks = []; function record(name, ok, details = {}) { checks.push({ name, ok: Boolean(ok), ...details }); const mark = ok ? 'PASS' : 'FAIL'; console.log(`${mark} ${name}${details.detail ? ` — ${details.detail}` : ''}`); } async function login() { const res = await fetch(`${base}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username, password }), }); const cookie = res.headers.get('set-cookie')?.split(';', 1)[0] ?? ''; record('login', res.ok && cookie, { detail: `status=${res.status}` }); return cookie; } async function headOrGet(url, { cookie } = {}) { const res = await fetch(url, { headers: cookie ? { Cookie: cookie } : {}, redirect: 'manual', }); const text = res.status >= 400 ? await res.text().catch(() => '') : ''; return { status: res.status, text: text.slice(0, 120) }; } async function waitRun(cookie, sessionId, runId) { for (let i = 0; i < 60; i += 1) { const res = await fetch(`${base}/api/agent/runs/${runId}`, { headers: { Cookie: cookie } }); const json = await res.json().catch(() => null); const status = json?.run?.status; if (status === 'succeeded' || status === 'failed') return { status, run: json?.run }; await new Promise((r) => setTimeout(r, 1500)); } return { status: 'timeout' }; } async function generateTestPage(cookie) { const started = await fetch(`${base}/api/agent/start`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: '{}', }); const sessionId = (await started.json()).id; const requestId = crypto.randomUUID(); const prompt = `请用 write_file 创建 public/verify-deliver-${Date.now()}.html,标题「发布链路验证页」,内容简单即可。完成后只回复「已生成」。`; const created = await fetch(`${base}/api/agent/runs`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookie }, body: JSON.stringify({ session_id: sessionId, request_id: requestId, user_message: { role: 'user', created: Math.floor(Date.now() / 1000), content: [{ type: 'text', text: prompt }], metadata: { userVisible: true, agentVisible: true, displayText: prompt }, }, }), }); const runId = (await created.json())?.run?.id; const finished = await waitRun(cookie, sessionId, runId); record('agent_run_generate_page', finished.status === 'succeeded', { detail: `run=${runId} status=${finished.status}`, }); return { sessionId, runId, finished }; } async function findLatestVerifyPage(cookie) { const res = await fetch(`${base}/api/mindspace/v1/pages?limit=20`, { headers: { Cookie: cookie }, }); const json = await res.json().catch(() => null); const items = json?.data ?? []; const match = items.find((p) => /发布链路验证|verify-deliver/i.test(String(p.title ?? ''))) ?? items.find((p) => String(p.workspaceRelativePath ?? '').includes('verify-deliver')); return match ?? items[0] ?? null; } async function main() { console.log(`\n=== Publication delivery verify @ ${base} ===\n`); const cookie = await login(); // 1. Existing publication route (/u/...) for (const slug of ['page-2b201736', 'page-0360d67a']) { const url = `${base}/u/john/pages/${slug}`; const { status, text } = await headOrGet(url); record(`publication_route /u/john/pages/${slug}`, status === 200, { detail: `HTTP ${status}${status >= 500 ? ` ${text}` : ''}`, }); } // 2. File layer (/MindSpace/.../public/...) const fileUrl = `${base}/MindSpace/${userId}/public/supplier-submit.html`; const fileRes = await headOrGet(fileUrl); record('file_layer MindSpace/public/supplier-submit.html', fileRes.status === 200, { detail: `HTTP ${fileRes.status}`, }); // 3. Runtime public base const runtime = await fetch(`${base}/api/runtime/status`).then((r) => r.json()).catch(() => null); const publicBase = runtime?.publicBaseUrl ?? runtime?.publish?.publicBaseUrl ?? null; record('runtime public base uses portal', String(publicBase ?? '').includes(':8081'), { detail: publicBase ?? 'missing', }); // 4. Generate new page and check publication closure await generateTestPage(cookie); await new Promise((r) => setTimeout(r, 2000)); const page = await findLatestVerifyPage(cookie); if (page) { const detail = await fetch(`${base}/api/mindspace/v1/pages/${page.id}`, { headers: { Cookie: cookie }, }).then((r) => r.json()).catch(() => null); const pub = detail?.data?.publication; record('new_page_has_publication', Boolean(pub?.id && pub?.status === 'online'), { detail: pub ? `${pub.status} ${pub.publicUrl ?? ''}` : 'no publication', }); if (pub?.publicUrl) { const pubPath = pub.publicUrl.replace(/^https?:\/\/[^/]+/, ''); const pubCheck = await headOrGet(`${base}${pubPath.startsWith('/') ? pubPath : `/${pubPath}`}`); record('new_page_publication_url_accessible', pubCheck.status === 200, { detail: `HTTP ${pubCheck.status} ${pub.publicUrl}`, }); } const rel = page.workspaceRelativePath ?? page.publicationUrl; if (page.workspaceRelativePath) { const wsUrl = `${base}/MindSpace/${userId}/${page.workspaceRelativePath}`; const ws = await headOrGet(wsUrl); record('new_page_file_layer', ws.status === 200, { detail: `HTTP ${ws.status}` }); } } else { record('new_page_found_in_list', false, { detail: 'no matching page after generate' }); } const ok = checks.every((c) => c.ok); console.log(`\n=== ${ok ? 'ALL PASS' : 'SOME FAILED'} (${checks.filter((c) => c.ok).length}/${checks.length}) ===\n`); process.exitCode = ok ? 0 : 1; } main().catch((err) => { console.error(err); process.exitCode = 1; });