#!/usr/bin/env node /** * 验证 john4 客户下单系统 Page Data 链路(insert + admin read)。 */ import { loadH5Environment } from './load-env.mjs'; import { createReporter, loginViaApi, resolvePortalBase, } from './scenario-test-lib.mjs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { PUBLISH_ROOT_DIR } from '../user-publish.mjs'; import fs from 'node:fs/promises'; const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); const USER_ID = process.env.CUSTOMER_ORDER_USER_ID ?? '32035858-9a20-425b-89da-c118ef0779aa'; const DATASET = 'customer_orders'; const FORM_PAGE = 'customer-order-form.html'; const ADMIN_PAGE = 'customer-order-admin.html'; loadH5Environment(import.meta.dirname); async function findPageIdFromPolicy(publishKey) { const policyDir = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'page-data-policies'); const entries = await fs.readdir(policyDir); for (const name of entries.filter((item) => item.endsWith('.json'))) { const raw = await fs.readFile(path.join(policyDir, name), 'utf8'); const policy = JSON.parse(raw); if (policy?.datasets?.[DATASET]?.insert) { return { pageId: policy.pageId, fileName: name }; } } return null; } async function main() { const port = Number(process.env.H5_PORT ?? 8081); const baseUrl = resolvePortalBase(port); const reporter = createReporter(); console.log('==> 客户下单系统 Page Data 验证'); console.log(` Portal: ${baseUrl}`); console.log(` 用户: ${USER_ID}\n`); const auth = await loginViaApi(baseUrl, { username: 'john4', password: '888888' }, reporter); const publishKey = auth.user?.id ?? USER_ID; const formUrl = `${baseUrl}/MindSpace/${publishKey}/public/${FORM_PAGE}`; const adminUrl = `${baseUrl}/MindSpace/${publishKey}/public/${ADMIN_PAGE}`; for (const [label, url] of [['下单页', formUrl], ['后台页', adminUrl]]) { const res = await fetch(url, { headers: { Cookie: auth.cookie } }); const html = await res.text(); if (res.status !== 200) { reporter.fail(`${label} HTTP`, `${res.status}`); } else { reporter.pass(`${label} HTTP 200`, url); } if (!html.includes('page-data-client.js')) { reporter.fail(`${label} 脚本`, '缺少 page-data-client.js'); } else { reporter.pass(`${label} Page Data 客户端`, '已引用'); } } const formMeta = await findPageIdFromPolicy(publishKey); if (!formMeta?.pageId) { reporter.fail('Page Data policy', '未找到 customer_orders insert policy'); process.exit(reporter.summary()); } reporter.pass('Page Data policy', `${formMeta.fileName} pageId=${formMeta.pageId}`); const insertPayload = { customer_name: '测试客户', phone: '13800138000', product_name: '演示商品A', quantity: '2', address: '上海市浦东新区测试路 1 号', remark: '自动化验证', status: '待处理', }; const insertRes = await fetch( `${baseUrl}/api/public/pages/${formMeta.pageId}/data/${DATASET}/rows`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: auth.cookie }, body: JSON.stringify(insertPayload), }, ); const insertBody = await insertRes.json().catch(() => ({})); if (!insertRes.ok) { reporter.fail('下单 insert', `${insertRes.status} ${JSON.stringify(insertBody)}`); } else { reporter.pass('下单 insert', `row id=${insertBody?.data?.id ?? insertBody?.id ?? 'ok'}`); } const adminMeta = await (async () => { const policyDir = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'page-data-policies'); const entries = await fs.readdir(policyDir); for (const name of entries.filter((item) => item.endsWith('.json'))) { const policy = JSON.parse(await fs.readFile(path.join(policyDir, name), 'utf8')); if (policy?.datasets?.[DATASET]?.read) return policy.pageId; } return null; })(); if (!adminMeta) { reporter.fail('后台 policy', '未找到 read policy'); } else { const tokenRes = await fetch(`${baseUrl}/api/public/pages/${adminMeta}/data-auth`, { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: auth.cookie }, body: JSON.stringify({ password: '88888888' }), }); const tokenBody = await tokenRes.json().catch(() => ({})); const token = tokenBody?.data?.token ?? tokenBody?.token; if (!tokenRes.ok || !token) { reporter.fail('后台口令认证', `${tokenRes.status}`); } else { const listRes = await fetch( `${baseUrl}/api/public/pages/${adminMeta}/data/${DATASET}?limit=20`, { headers: { 'x-page-data-token': token, Cookie: auth.cookie } }, ); const listBody = await listRes.json().catch(() => ({})); const rows = listBody?.data?.rows ?? listBody?.rows ?? []; const hit = rows.some((row) => row.customer_name === insertPayload.customer_name && row.product_name === insertPayload.product_name); if (!listRes.ok || !hit) { reporter.fail('后台读订单', `rows=${rows.length}, 未找到测试订单`); } else { reporter.pass('后台读订单', `共 ${rows.length} 条,含测试订单`); } } } const sqlitePath = path.join(repoRoot, PUBLISH_ROOT_DIR, publishKey, '.mindspace', 'private-data.sqlite'); try { await fs.stat(sqlitePath); reporter.pass('用户数据空间', 'private-data.sqlite 存在'); } catch { reporter.pass('用户数据空间', 'PostgreSQL 模式(无本地 sqlite 文件)'); } console.log('\n--- 访问入口 ---'); console.log(`下单页: ${formUrl}`); console.log(`后台页: ${adminUrl} (口令 88888888)`); process.exit(reporter.summary()); } main().catch((err) => { console.error(err instanceof Error ? err.stack ?? err.message : err); process.exit(1); });