fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config. Co-authored-by: Cursor <cursoragent@cursor.com>
133 lines
4.7 KiB
JavaScript
133 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Local smoke test for Portal template catalog API (Memind 8081).
|
||
* Usage: node scripts/verify-template-catalog-portal.mjs
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { loadH5Environment } from './load-env.mjs';
|
||
|
||
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
||
const root = path.join(scriptDir, '..');
|
||
|
||
function loadEnvFile(filePath) {
|
||
if (!fs.existsSync(filePath)) return;
|
||
for (const line of fs.readFileSync(filePath, 'utf8').split('\n')) {
|
||
const trimmed = line.trim();
|
||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||
const eq = trimmed.indexOf('=');
|
||
if (eq <= 0) continue;
|
||
const key = trimmed.slice(0, eq).trim();
|
||
let value = trimmed.slice(eq + 1).trim();
|
||
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
||
value = value.slice(1, -1);
|
||
}
|
||
if (!(key in process.env)) process.env[key] = value;
|
||
}
|
||
}
|
||
|
||
loadH5Environment(scriptDir);
|
||
loadEnvFile(path.join(root, '../memind_adm/.env'));
|
||
|
||
const base = process.env.PORTAL_API_BASE ?? `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`;
|
||
|
||
const username =
|
||
process.env.VERIFY_PORTAL_USERNAME?.trim() ||
|
||
process.env.H5_TEST_USERNAME?.trim() ||
|
||
process.env.MEMIND_E2E_USERNAME?.trim() ||
|
||
process.env.H5_ADMIN_USERNAME?.trim() ||
|
||
'admin';
|
||
const password =
|
||
process.env.VERIFY_PORTAL_PASSWORD?.trim() ||
|
||
process.env.H5_TEST_PASSWORD?.trim() ||
|
||
process.env.MEMIND_E2E_PASSWORD?.trim() ||
|
||
process.env.JOHN_PASSWORD?.trim() ||
|
||
process.env.VERIFY_PASSWORD?.trim() ||
|
||
process.env.H5_ADMIN_PASSWORD?.trim() ||
|
||
process.env.ADMIN_PASSWORD?.trim() ||
|
||
'';
|
||
|
||
async function request(method, urlPath, { body, cookie } = {}) {
|
||
const res = await fetch(`${base}${urlPath}`, {
|
||
method,
|
||
headers: {
|
||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||
...(cookie ? { Cookie: cookie } : {}),
|
||
},
|
||
body: body ? JSON.stringify(body) : undefined,
|
||
});
|
||
const text = await res.text();
|
||
let json = null;
|
||
try {
|
||
json = text ? JSON.parse(text) : null;
|
||
} catch {
|
||
json = { raw: text.slice(0, 300) };
|
||
}
|
||
return { status: res.status, json, text, setCookie: res.headers.getSetCookie?.() ?? [] };
|
||
}
|
||
|
||
function cookieFromSetCookie(setCookie) {
|
||
return setCookie.map((c) => c.split(';')[0]).join('; ');
|
||
}
|
||
|
||
function assertOk(cond, msg) {
|
||
if (!cond) throw new Error(msg);
|
||
}
|
||
|
||
async function main() {
|
||
console.log(`==> Portal API base: ${base}`);
|
||
|
||
if (!password) {
|
||
console.error('缺少 Portal 登录密码(VERIFY_PORTAL_PASSWORD / H5_ADMIN_PASSWORD / memind_adm/.env)');
|
||
process.exit(1);
|
||
}
|
||
|
||
const login = await request('POST', '/auth/login', {
|
||
body: { username, password },
|
||
});
|
||
assertOk(login.status === 200, `登录失败 HTTP ${login.status}: ${JSON.stringify(login.json)}`);
|
||
const cookie = cookieFromSetCookie(login.setCookie);
|
||
assertOk(cookie, '登录未返回 session cookie');
|
||
console.log(`✓ Portal 登录 (${username})`);
|
||
|
||
const catalog = await request('GET', '/api/mindspace/v1/template-catalog', { cookie });
|
||
assertOk(catalog.status === 200, `catalog HTTP ${catalog.status}: ${JSON.stringify(catalog.json)}`);
|
||
const items = catalog.json?.data?.items ?? catalog.json?.items ?? [];
|
||
assertOk(Array.isArray(items), 'catalog items 应为数组');
|
||
assertOk(items.length >= 13, `应至少 13 个模板,实际 ${items.length}`);
|
||
const travel = items.find((item) => item.skillName === 'page-template-travel');
|
||
assertOk(travel, '缺少 page-template-travel');
|
||
console.log(
|
||
`✓ GET template-catalog (${items.length} 项,travel owned=${Boolean(travel.owned)} price=${travel.priceCents})`,
|
||
);
|
||
|
||
const preview = await request('GET', '/api/mindspace/v1/template-catalog/page-template-travel/preview', {
|
||
cookie,
|
||
});
|
||
assertOk(preview.status === 200, `preview HTTP ${preview.status}`);
|
||
assertOk(String(preview.text).includes('周末去哪玩'), '预览 HTML 应含示例文案');
|
||
console.log('✓ GET template preview HTML');
|
||
|
||
if (travel.owned) {
|
||
console.log('• travel 已拥有,跳过余额购买测试');
|
||
} else if (travel.priceCents > 0) {
|
||
const purchase = await request('POST', '/api/mindspace/v1/template-catalog/page-template-travel/purchase', {
|
||
cookie,
|
||
});
|
||
if (purchase.status === 402) {
|
||
console.log('• 余额不足 (402),purchase 路由正常');
|
||
} else {
|
||
assertOk(purchase.status === 200, `purchase HTTP ${purchase.status}: ${JSON.stringify(purchase.json)}`);
|
||
console.log('✓ POST balance purchase 成功');
|
||
}
|
||
}
|
||
|
||
console.log('\n全部 Portal template-catalog API 联调通过。');
|
||
}
|
||
|
||
main().catch((err) => {
|
||
console.error('\n联调失败:', err.message);
|
||
process.exit(1);
|
||
});
|