test: write stable launch persona evidence

This commit is contained in:
Codex
2026-07-01 08:45:20 +08:00
parent cf1ae6026b
commit 91abf854fe
8 changed files with 176 additions and 27 deletions

View File

@@ -0,0 +1,48 @@
import assert from 'node:assert/strict';
import path from 'node:path';
import { parseCliArgs } from './launch-persona-smoke.js';
const repoRoot = process.cwd();
const writeOnly = parseCliArgs(['--write', 'docs/refactor/launch-artifacts/launch-persona-smoke.json']);
assert.equal(
writeOnly.writePath,
path.resolve(repoRoot, 'docs/refactor/launch-artifacts/launch-persona-smoke.json'),
'--write should resolve stable JSON evidence paths from the repo root',
);
assert.equal(writeOnly.writeMdPath, '', '--write should not imply a stable Markdown report path');
const equalsForm = parseCliArgs([
'--output-dir=docs/refactor/launch-artifacts',
'--write=docs/refactor/launch-artifacts/launch-persona-smoke.json',
'--write-md=docs/refactor/launch-artifacts/launch-persona-smoke.md',
'--json',
]);
assert.equal(
equalsForm.outputDir,
path.resolve(repoRoot, 'docs/refactor/launch-artifacts'),
'--output-dir should resolve stable report directories',
);
assert.equal(
equalsForm.writePath,
path.resolve(repoRoot, 'docs/refactor/launch-artifacts/launch-persona-smoke.json'),
'--write= should resolve stable JSON evidence paths',
);
assert.equal(
equalsForm.writeMdPath,
path.resolve(repoRoot, 'docs/refactor/launch-artifacts/launch-persona-smoke.md'),
'--write-md= should resolve stable Markdown evidence paths',
);
assert.throws(
() => parseCliArgs(['--write']),
/requires a file path value/,
'--write without a value should fail fast',
);
assert.throws(
() => parseCliArgs(['--unknown']),
/Unknown launch persona smoke option/,
'unknown options should fail fast instead of silently dropping evidence paths',
);
console.log('[PASS] launch persona smoke cli options');

View File

@@ -4,11 +4,16 @@ import { spawn } from 'node:child_process';
import fs from 'node:fs/promises';
import net from 'node:net';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import pg from 'pg';
const DEFAULT_DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
const cliOptions = parseCliArgs(process.argv.slice(2));
const databaseUrl = process.env.DATABASE_URL || DEFAULT_DATABASE_URL;
const outputDir = process.env.LAUNCH_SMOKE_OUTPUT_DIR || 'docs/refactor/launch-artifacts';
const outputDir =
cliOptions.outputDir || resolvePathOption(process.env.LAUNCH_SMOKE_OUTPUT_DIR || 'docs/refactor/launch-artifacts');
const stableJsonPath = cliOptions.writePath || resolvePathOption(process.env.LAUNCH_SMOKE_WRITE_PATH || '');
const stableMdPath = cliOptions.writeMdPath || resolvePathOption(process.env.LAUNCH_SMOKE_WRITE_MD_PATH || '');
const startServer = boolEnv('LAUNCH_SMOKE_START_SERVER', !process.env.LAUNCH_SMOKE_API_BASE);
const fixedPort = Number(process.env.LAUNCH_SMOKE_API_PORT || 0) || 0;
const authMode = normalizeAuthMode(process.env.LAUNCH_SMOKE_AUTH_MODE || 'app_session');
@@ -29,6 +34,62 @@ let serverProcess = null;
let serverLogs = '';
let authTokens = {};
function resolvePathOption(value) {
if (!value) return '';
return path.isAbsolute(value) ? value : path.resolve(process.cwd(), value);
}
function readValueArg(argv, index, flag) {
const value = argv[index + 1];
if (!value || value.startsWith('--')) throw new Error(`${flag} requires a file path value.`);
return value;
}
function parseCliArgs(argv = []) {
const options = {
outputDir: '',
writePath: '',
writeMdPath: '',
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--json') {
continue;
}
if (arg === '--output-dir') {
options.outputDir = resolvePathOption(readValueArg(argv, index, arg));
index += 1;
continue;
}
if (arg.startsWith('--output-dir=')) {
options.outputDir = resolvePathOption(arg.slice('--output-dir='.length));
continue;
}
if (arg === '--write') {
options.writePath = resolvePathOption(readValueArg(argv, index, arg));
index += 1;
continue;
}
if (arg.startsWith('--write=')) {
options.writePath = resolvePathOption(arg.slice('--write='.length));
continue;
}
if (arg === '--write-md') {
options.writeMdPath = resolvePathOption(readValueArg(argv, index, arg));
index += 1;
continue;
}
if (arg.startsWith('--write-md=')) {
options.writeMdPath = resolvePathOption(arg.slice('--write-md='.length));
continue;
}
throw new Error(`Unknown launch persona smoke option: ${arg}`);
}
return options;
}
function boolEnv(key, fallback) {
const value = process.env[key];
if (value === undefined || value === '') return fallback;
@@ -483,7 +544,8 @@ async function writeReport(report) {
await fs.mkdir(outputDir, { recursive: true });
const jsonPath = path.join(outputDir, `launch-persona-smoke-${shanghaiStamp()}.json`);
const mdPath = jsonPath.replace(/\.json$/, '.md');
await fs.writeFile(jsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
const jsonPayload = `${JSON.stringify(report, null, 2)}\n`;
await fs.writeFile(jsonPath, jsonPayload, 'utf8');
const lines = [
'# 上线前角色旅程烟测报告',
'',
@@ -498,8 +560,19 @@ async function writeReport(report) {
'说明:该脚本会写入少量 `launch_persona_smoke` 测试权益、练习 session、答题和收藏记录。生产环境仅建议在灰度/演练租户运行。',
'',
];
await fs.writeFile(mdPath, `${lines.join('\n')}\n`, 'utf8');
return { jsonPath, mdPath };
const mdPayload = `${lines.join('\n')}\n`;
await fs.writeFile(mdPath, mdPayload, 'utf8');
if (stableJsonPath) {
await fs.mkdir(path.dirname(stableJsonPath), { recursive: true });
await fs.writeFile(stableJsonPath, jsonPayload, 'utf8');
}
if (stableMdPath) {
await fs.mkdir(path.dirname(stableMdPath), { recursive: true });
await fs.writeFile(stableMdPath, mdPayload, 'utf8');
}
return { jsonPath, mdPath, stableJsonPath, stableMdPath };
}
async function runStep(name, fn) {
@@ -543,6 +616,8 @@ async function main() {
console.log(`[launch-smoke] status=${report.status}`);
console.log(`[launch-smoke] wrote ${files.jsonPath}`);
console.log(`[launch-smoke] wrote ${files.mdPath}`);
if (files.stableJsonPath) console.log(`[launch-smoke] wrote ${files.stableJsonPath}`);
if (files.stableMdPath) console.log(`[launch-smoke] wrote ${files.stableMdPath}`);
if (report.status !== 'pass') process.exitCode = 1;
} finally {
await pool.end();
@@ -550,9 +625,13 @@ async function main() {
}
}
main().catch(error => {
console.error(error);
if (serverLogs) console.error(serverLogs);
stopServer();
process.exit(1);
});
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
main().catch(error => {
console.error(error);
if (serverLogs) console.error(serverLogs);
stopServer();
process.exit(1);
});
}
export { parseCliArgs };