Files
gongxue-base/scripts/production-launch-gate-test.js
2026-06-30 02:31:25 +08:00

120 lines
4.4 KiB
JavaScript

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');