import assert from 'node:assert/strict' import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import os from 'node:os' import path from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' import test from 'node:test' const testDir = path.dirname(fileURLToPath(import.meta.url)) const projectRoot = path.resolve(testDir, '..', '..') const verifierPath = path.join(projectRoot, 'scripts', 'verify-aug1-content-candidates.mjs') const candidatePath = path.join( projectRoot, 'docs', 'content-candidates', 'aug1-life-advisor-content-candidates-v0.1.json', ) const baseCandidate = JSON.parse(await readFile(candidatePath, 'utf8')) const cloneCandidate = () => structuredClone(baseCandidate) async function verifyCandidate(candidate) { const tempDir = await mkdtemp(path.join(os.tmpdir(), 'aug1-content-')) const fixturePath = path.join(tempDir, 'candidate.json') try { await writeFile(fixturePath, JSON.stringify(candidate), 'utf8') return spawnSync(process.execPath, [verifierPath, fixturePath], { cwd: projectRoot, encoding: 'utf8', }) } finally { await rm(tempDir, { recursive: true, force: true }) } } test('accepts the reviewed disabled candidate structure through an explicit package path', async () => { const result = await verifyCandidate(cloneCandidate()) assert.equal(result.status, 0, result.stderr) assert.match(result.stdout, /verification passed/) }) test('rejects sensitive locators, credentials and mobile numbers', async (t) => { const cases = [ ['precise room', (candidate) => { candidate.scenarios[0].scenarioDraft.openingText = '住户住在3栋2单元501室。' }], ['access credential', (candidate) => { candidate.scenarios[0].scenarioDraft.openingText = '门禁口令 ABCD。' }], ['mobile phone', (candidate) => { candidate.policyQuestionCandidates[0].question = ['联系号码', '13', '9000', '00000'].join('') }], ] for (const [name, mutate] of cases) { await t.test(name, async () => { const candidate = cloneCandidate() mutate(candidate) const result = await verifyCandidate(candidate) assert.notEqual(result.status, 0) }) } }) test('rejects publishable content, answers and duplicate IDs before formal sign-off', async (t) => { const cases = [ ['publishable root', (candidate) => { candidate.publishable = true }], ['embedded answer', (candidate) => { candidate.policyQuestionCandidates[0].answer = 'not allowed before formal sourcing' }], ['duplicate scenario ID', (candidate) => { candidate.scenarios[1].candidateId = candidate.scenarios[0].candidateId }], ['duplicate question ID', (candidate) => { candidate.policyQuestionCandidates[1].id = candidate.policyQuestionCandidates[0].id }], ] for (const [name, mutate] of cases) { await t.test(name, async () => { const candidate = cloneCandidate() mutate(candidate) const result = await verifyCandidate(candidate) assert.notEqual(result.status, 0) }) } })