import assert from "node:assert/strict"; import { chmodSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; import { spawnSync } from "node:child_process"; import test from "node:test"; const repoRoot = path.resolve(new URL("../../..", import.meta.url).pathname); const sourceScript = path.join(repoRoot, "scripts/check-minimal.sh"); const localCandidateRunner = path.join(repoRoot, "scripts/qa/run-local-candidate.sh"); function writeExecutable(file, source) { mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, source, "utf8"); chmodSync(file, 0o755); } test("local-candidate 基础 profile 在没有 issues 目录时不调用历史 CSV 门禁", () => { const fixtureRoot = mkdtempSync(path.join(tmpdir(), "hyb-local-candidate-profile-")); try { const scriptSource = readFileSync(sourceScript, "utf8"); const fixtureScript = path.join(fixtureRoot, "scripts/check-minimal.sh"); writeExecutable(fixtureScript, scriptSource); mkdirSync(path.join(fixtureRoot, "server"), { recursive: true }); mkdirSync(path.join(fixtureRoot, "client"), { recursive: true }); for (const match of scriptSource.matchAll(/^\s*bash(?: -n)? ([^\s]+)/gmu)) { const relativeFile = match[1]; for (const base of [fixtureRoot, path.join(fixtureRoot, "server")]) { const file = path.join(base, relativeFile); mkdirSync(path.dirname(file), { recursive: true }); writeFileSync(file, "#!/usr/bin/env bash\n", "utf8"); } } const binDir = path.join(fixtureRoot, "bin"); const commandLog = path.join(fixtureRoot, "commands.log"); writeExecutable(path.join(binDir, "node"), `#!/usr/bin/env bash printf 'node %s\\n' "$*" >> "$CHECK_MINIMAL_COMMAND_LOG" if [[ "$*" == *check-process-contract.mjs* ]]; then exit 91; fi exit 0 `); writeExecutable(path.join(binDir, "npm"), `#!/usr/bin/env bash printf 'npm %s\\n' "$*" >> "$CHECK_MINIMAL_COMMAND_LOG" exit 0 `); writeExecutable(path.join(binDir, "python3"), `#!/usr/bin/env bash printf 'python3 %s\\n' "$*" >> "$CHECK_MINIMAL_COMMAND_LOG" exit 92 `); const result = spawnSync("bash", [fixtureScript, "local-candidate"], { cwd: fixtureRoot, encoding: "utf8", env: { ...process.env, PATH: `${binDir}:${process.env.PATH ?? ""}`, CHECK_MINIMAL_COMMAND_LOG: commandLog, }, }); assert.equal(result.status, 0, `${result.stdout}\n${result.stderr}`); const commands = readFileSync(commandLog, "utf8"); assert.doesNotMatch(commands, /check-process-contract|validate-feature-matrix|python3/u); assert.equal(existsSync(path.join(fixtureRoot, "issues")), false); } finally { rmSync(fixtureRoot, { recursive: true, force: true }); } }); test("local-candidate 结果标签严格区分 full、focused 与 list", () => { const source = readFileSync(localCandidateRunner, "utf8"); const blockStart = source.lastIndexOf('if [[ "$MODE" == "full" ]]; then'); const blockEnd = source.indexOf("\nfi", blockStart); assert.notEqual(blockStart, -1, "缺少 full/focused 结果标签分支"); assert.notEqual(blockEnd, -1, "full/focused 结果标签分支不完整"); const resultLabelBlock = source.slice(blockStart, blockEnd + "\nfi".length); const runLabelBlock = (mode) => spawnSync("bash", ["-c", resultLabelBlock], { encoding: "utf8", env: { ...process.env, MODE: mode }, }); const full = runLabelBlock("full"); const focused = runLabelBlock("focused"); assert.equal(full.status, 0, full.stderr); assert.match(full.stdout, /LOCAL_CANDIDATE_PASS/u); assert.doesNotMatch(full.stdout, /FOCUSED_BROWSER_PASS/u); assert.equal(focused.status, 0, focused.stderr); assert.match(focused.stdout, /FOCUSED_BROWSER_PASS/u); assert.doesNotMatch(focused.stdout, /LOCAL_CANDIDATE_PASS/u); assert.equal(source.match(/LOCAL_CANDIDATE_PASS/gu)?.length, 1); assert.match( source, /if \[\[ "\$MODE" == "list" \]\]; then[\s\S]*?LIST_ONLY[\s\S]*?exit 0[\s\S]*?\nfi/u, ); });