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