Files
memind/scripts/verify-publication-delivery.mjs

161 lines
6.2 KiB
JavaScript

#!/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;
});