/** * test/e2e.test.mjs — 로컬 종단 간 검증 진입점. * * 실제 HAL·포털·Node Agent 프로세스와 가짜 Webmaker/AriMail/메일 서버를 띄우고 test/e2e/flows/*.mjs 의 흐름을 순서대로 실행한 뒤 * docs/verification/local-e2e-<날짜>.md/.json 에 흐름별 판정과 결함을 기록한다. * ARIWEB_E2E=0 건너뜀 * ARIWEB_E2E_FLOWS=a,b 특정 흐름 그룹만(파일 이름) * ARIWEB_E2E_STRICT=1 'failed' 판정이 있으면 종료 코드 1(기본은 보고서만 쓰고 통과 — 결함은 보고서가 알린다) * ARIWEB_E2E_KEEP=1 임시 디렉터리 보존 * 전체 5분 안에 끝나도록 흐름마다 시간 제한을 둔다. */ import { test, ok, done } from '../lib/test.mjs'; import { readdirSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import { bootEnv } from './e2e/boot.mjs'; import { createReport } from './e2e/report.mjs'; if (process.env.ARIWEB_E2E === '0') { console.log(' (ARIWEB_E2E=0 — 로컬 종단 간 검증 건너뜀)'); done(); } else { const HERE = dirname(fileURLToPath(import.meta.url)); const report = createReport(); const startedAt = Date.now(); const log = (...a) => console.log(' ', ...a); const only = (process.env.ARIWEB_E2E_FLOWS || '').split(',').map((s) => s.trim()).filter(Boolean); const files = readdirSync(join(HERE, 'e2e', 'flows')).filter((f) => f.endsWith('.mjs')).sort().filter((f) => !only.length || only.some((o) => f.includes(o))); let env = null; const state = {}; await test('환경 기동: HAL·Agent 4대·포털·가짜 서비스', async () => { env = await bootEnv({ log }); report.env.agents = env.agents.length; report.env.ports = env.ports; report.env.mailServerRegistered = env.mailServerRegistered; report.env.node = process.version; ok(env.hal.proc.exited === null, 'HAL 실행 중'); ok(env.portal.proc.exited === null, '포털 실행 중'); const h = await env.ops.get('/api/health'); ok(h.json.bootstrap.complete, '부트스트랩 완료: ' + JSON.stringify(h.json.bootstrap)); if (!env.mailServerRegistered) report.bug({ title: '운영자 중계로 AriMail 서버 등록 실패', file: 'portal/mail/api.mjs', route: 'POST /ops/api/portal/mail/servers → /internal/ops/mail/servers', expected: '200 server', actual: env.mailServerResponse, repro: 'boot.mjs 의 AriMail 서버 등록' }); }); for (const f of files) { await test(`흐름 그룹 ${f}`, async () => { if (!env) throw new Error('환경 없음'); const mod = await import(join(HERE, 'e2e', 'flows', f)); const budget = Number(mod.BUDGET_MS || 60000); let timer; const timeout = new Promise((_, rej) => { timer = setTimeout(() => rej(new Error(`흐름 그룹 ${f} 시간 초과(${budget}ms)`)), budget); }); try { await Promise.race([mod.run({ env, report, state, log }), timeout]); } finally { clearTimeout(timer); } log(`${f}: ${Math.round((Date.now() - startedAt) / 1000)}s 경과`); }); } await test('보고서 기록', async () => { const out = report.write(join(HERE, '..', 'docs', 'verification')); log(`보고서: ${out.md}`); log('요약: ' + JSON.stringify(out.summary)); log('결함: ' + report.bugs.length + '건'); if (process.env.ARIWEB_E2E_STRICT === '1') ok(out.summary.failed === 0, `failed 판정 ${out.summary.failed}건`); }); if (env) await env.stop(); done(); }