import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; import { gateChecks, requiredAttestations } from './production-launch-gate.js'; const repoRoot = process.cwd(); const scriptPath = path.join(repoRoot, 'scripts', 'production-launch-gate.js'); function isoNow() { return new Date().toISOString(); } function createEvidence(tempDir, overrides = {}) { const artifactDir = path.join(tempDir, 'launch-artifacts'); fs.mkdirSync(artifactDir, { recursive: true }); const checks = gateChecks.map(spec => { const artifact = `launch-artifacts/${spec.id}.log`; fs.writeFileSync(path.join(tempDir, artifact), `[PASS] ${spec.id}\n`, 'utf8'); return { id: spec.id, status: 'pass', command: `npm run ${spec.commandIncludes} -- recorded-for-launch-gate`, completedAt: isoNow(), artifact, summary: { ...spec.summary }, }; }); const attestations = requiredAttestations.map(spec => ({ id: spec.id, status: 'approved', approver: 'test-owner', approvedAt: isoNow(), notes: spec.label, })); return { schemaVersion: 1, environment: 'production', commit: '52cef9fabcd1234567890abcdef1234567890abc', target: { apiBaseUrl: 'https://api.gongxue100.com', studentH5Url: 'https://www.gongxue100.com', tenantAdminH5Url: 'https://admin.gongxue100.com', platformAdminH5Url: 'https://console.gongxue100.com', }, checks, attestations, ...overrides, }; } function runGate(evidence, options = {}) { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tiku-launch-gate-')); const evidencePath = path.join(tempDir, 'evidence.json'); const finalEvidence = typeof evidence === 'function' ? evidence(tempDir) : evidence; fs.writeFileSync(evidencePath, JSON.stringify(finalEvidence, null, 2), 'utf8'); const result = spawnSync(process.execPath, [scriptPath, '--evidence', evidencePath, '--json', ...(options.args || [])], { cwd: repoRoot, encoding: 'utf8', env: { PATH: process.env.PATH || '', Path: process.env.Path || '', SystemRoot: process.env.SystemRoot || '', ComSpec: process.env.ComSpec || '', TEMP: process.env.TEMP || os.tmpdir(), TMP: process.env.TMP || os.tmpdir(), }, }); const payload = JSON.parse(result.stdout || '{}'); fs.rmSync(tempDir, { recursive: true, force: true }); return { ...result, payload }; } const safe = runGate(tempDir => createEvidence(tempDir)); assert.equal(safe.status, 0, `complete launch evidence should pass: ${safe.stdout} ${safe.stderr}`); assert.equal(safe.payload.summary?.blocker, 0, 'complete launch evidence should have no blockers'); const missingArtifact = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'auth.remote-smoke'); fs.rmSync(path.join(tempDir, item.artifact), { force: true }); return evidence; }); assert.notEqual(missingArtifact.status, 0, 'missing artifact should fail launch gate'); assert.ok( missingArtifact.payload.checks?.some(item => item.id === 'check.auth.remote-smoke.artifact' && item.status === 'blocker'), 'missing artifact should be reported as a blocker', ); const wrongMigrationProfile = runGate(tempDir => { const evidence = createEvidence(tempDir); const item = evidence.checks.find(check => check.id === 'migration.pb-production-dry-run'); item.summary.migrationProfile = 'development'; return evidence; }); assert.notEqual(wrongMigrationProfile.status, 0, 'development dry-run evidence should fail launch gate'); assert.ok( wrongMigrationProfile.payload.checks?.some(item => item.id === 'check.migration.pb-production-dry-run.summary' && item.status === 'blocker'), 'migration profile mismatch should be reported as a blocker', ); const missingAttestation = runGate(tempDir => { const evidence = createEvidence(tempDir); evidence.attestations = evidence.attestations.filter(item => item.id !== 'backup.snapshot'); return evidence; }); assert.notEqual(missingAttestation.status, 0, 'missing manual attestation should fail launch gate'); assert.ok( missingAttestation.payload.checks?.some(item => item.id === 'attestation.backup.snapshot' && item.status === 'blocker'), 'missing attestation should be reported as a blocker', ); console.log('[PASS] production launch gate');