Files

212 lines
7.5 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* E2E: Portal template catalog balance purchase + WeChat checkout creation.
* Usage: node scripts/verify-template-catalog-e2e.mjs
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadH5Environment } from './load-env.mjs';
import { createReporter, loginViaApi } from './scenario-test-lib.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 portalBase = process.env.PORTAL_API_BASE ?? `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`;
const adminBase = process.env.ADM_DEV_BACKEND ?? 'http://127.0.0.1:8085';
const adminUser = process.env.H5_ADMIN_USERNAME?.trim() || 'admin';
const adminPass =
process.env.H5_ADMIN_PASSWORD?.trim() ||
process.env.ADMIN_PASSWORD?.trim() ||
'';
const portalUser =
process.env.TEMPLATE_E2E_USERNAME?.trim() ||
process.env.MEMIND_E2E_USERNAME?.trim() ||
'john4';
const portalPass =
process.env.TEMPLATE_E2E_PASSWORD?.trim() ||
process.env.MEMIND_E2E_PASSWORD?.trim() ||
process.env.JOHN4_PASSWORD?.trim() ||
'888888';
const PURCHASE_SKILL = process.env.TEMPLATE_E2E_SKILL?.trim() || 'page-template-travel';
async function api(base, 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, 400) };
}
return { status: res.status, json, text };
}
async function main() {
const reporter = createReporter();
console.log(`==> Portal ${portalBase}`);
console.log(`==> Admin ${adminBase}`);
console.log(`==> 购买目标 skill: ${PURCHASE_SKILL}`);
if (!adminPass) {
throw new Error('缺少 H5_ADMIN_PASSWORD(memind_adm/.env)');
}
const adminAuth = await loginViaApi(adminBase, { username: adminUser, password: adminPass }, reporter);
let portalAuth;
try {
portalAuth = await loginViaApi(
portalBase,
{ username: portalUser, password: portalPass },
{
pass: (label, detail) => reporter.pass(`Portal ${label}`, detail),
fail: (label, detail) => reporter.fail(`Portal ${label}`, detail),
checks: reporter.checks,
issues: reporter.issues,
},
);
reporter.pass('Portal 测试账号', portalUser);
} catch (err) {
throw new Error(`Portal 测试账号 ${portalUser} 登录失败: ${err.message}`);
}
const rechargeRes = await api(adminBase, 'POST', `/admin-api/users/${portalAuth.user.id}/recharge`, {
cookie: adminAuth.cookie,
body: { amountCents: 5000, note: 'template-catalog-e2e' },
});
if (rechargeRes.status === 200) {
reporter.pass('Admin 充值', `+5000 分 → ${portalUser}`);
} else {
reporter.fail('Admin 充值', `HTTP ${rechargeRes.status}: ${JSON.stringify(rechargeRes.json)}`);
}
const catalogRes = await api(portalBase, 'GET', '/api/mindspace/v1/template-catalog', {
cookie: portalAuth.cookie,
});
if (catalogRes.status !== 200) {
throw new Error(`catalog HTTP ${catalogRes.status}: ${JSON.stringify(catalogRes.json)}`);
}
const items = catalogRes.json?.data?.items ?? [];
reporter.pass('模板列表', `${items.length} 项`);
const target = items.find((item) => item.skillName === PURCHASE_SKILL);
if (!target) {
throw new Error(`缺少 ${PURCHASE_SKILL}`);
}
const pickCheckoutSkill = () => {
const notOwned = items.filter((item) => !item.owned && item.priceCents > 0);
return notOwned[0]?.skillName ?? PURCHASE_SKILL;
};
if (target.owned) {
reporter.pass('余额购买', `跳过:${PURCHASE_SKILL} 已拥有`);
} else {
const meBefore = await api(portalBase, 'GET', '/auth/me', { cookie: portalAuth.cookie });
const balanceBefore = Number(meBefore.json?.user?.balance_cents ?? 0);
reporter.pass('购买前余额', `${balanceBefore} 分`);
const purchaseRes = await api(
portalBase,
'POST',
`/api/mindspace/v1/template-catalog/${encodeURIComponent(PURCHASE_SKILL)}/purchase`,
{ cookie: portalAuth.cookie },
);
if (purchaseRes.status === 402) {
reporter.fail('余额购买', `402 余额不足: ${JSON.stringify(purchaseRes.json)}`);
} else if (purchaseRes.status !== 200) {
reporter.fail('余额购买', `HTTP ${purchaseRes.status}: ${JSON.stringify(purchaseRes.json)}`);
} else {
const result = purchaseRes.json?.data ?? purchaseRes.json;
reporter.pass(
'余额购买',
`price=${result.priceCents ?? target.priceCents} balance=${result.balanceCents ?? '?'}`,
);
const skills = result.grantedSkills ?? [];
if (skills.includes(PURCHASE_SKILL)) {
reporter.pass('skill 开通', PURCHASE_SKILL);
} else {
reporter.fail('skill 开通', `grantedSkills 未含 ${PURCHASE_SKILL}: ${JSON.stringify(skills)}`);
}
}
}
const wechatSkill = pickCheckoutSkill();
const checkoutRes = await api(
portalBase,
'POST',
`/api/mindspace/v1/template-catalog/${encodeURIComponent(wechatSkill)}/checkout`,
{
cookie: portalAuth.cookie,
body: { payScene: 'native' },
},
);
const checkout = checkoutRes.json?.data ?? checkoutRes.json;
if (checkoutRes.status === 200 && checkout?.ok && checkout?.order) {
reporter.pass(
'微信 checkout',
`${wechatSkill} pending=${checkout.order.status} mode=${checkout.order.payMode}`,
);
if (checkout.order.codeUrl || checkout.order.jsapiParams || checkout.order.h5Url) {
reporter.pass('微信支付参数', checkout.order.codeUrl ? 'native codeUrl' : checkout.order.payMode);
}
} else if (checkoutRes.status === 400 && String(checkout?.error?.message ?? checkout?.message ?? '').includes('已拥有')) {
reporter.pass('微信 checkout', `${wechatSkill} 已拥有,跳过`);
} else if (
checkoutRes.status === 400 &&
String(checkout?.error?.message ?? checkout?.message ?? '').includes('微信支付尚未配置')
) {
reporter.pass('微信 checkout', '微信支付未配置(本地预期可接受)');
} else {
reporter.fail('微信 checkout', `HTTP ${checkoutRes.status}: ${JSON.stringify(checkoutRes.json)}`);
}
const previewRes = await api(
portalBase,
'GET',
`/api/mindspace/v1/template-catalog/page-template-travel/preview`,
{ cookie: portalAuth.cookie },
);
if (previewRes.status === 200 && previewRes.text.includes('周末去哪玩')) {
reporter.pass('预览 HTML', 'travel 示例文案正确');
} else {
reporter.fail('预览 HTML', `HTTP ${previewRes.status}`);
}
process.exitCode = reporter.summary();
}
main().catch((err) => {
console.error('\nE2E 失败:', err.message);
process.exit(1);
});