130 行
8.0 KiB
JavaScript
130 行
8.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { copyFileSync, mkdirSync, readFileSync, rmSync, writeFileSync, unlinkSync, symlinkSync } from "node:fs";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
import { validateExactProductionIdentity } from "../../../deploy/trialrun/production-preflight.mjs";
|
|
import { browserInventory, createDiagnosticScratch, sourceSnapshot, compareSnapshots } from "../full-local-diagnostic.mjs";
|
|
|
|
test("全仓快照发现已跟踪 PNG 覆盖、dirty 二次改写、index-only 和删除", context => {
|
|
const root = createDiagnosticScratch();
|
|
context.after(() => rmSync(root, { recursive: true }));
|
|
const git = args => { const result = spawnSync("git", args, { cwd: root, encoding: "utf8" }); assert.equal(result.status, 0, result.stderr); };
|
|
mkdirSync(path.join(root, "client")); mkdirSync(path.join(root, "output"));
|
|
writeFileSync(path.join(root, ".gitignore"), "output/\n");
|
|
writeFileSync(path.join(root, "client/source.ts"), "export {};\n");
|
|
const image = path.join(root, "output/archive.png"); writeFileSync(image, "archived");
|
|
git(["init", "-q"]); git(["add", "."]); git(["add", "-f", "output/archive.png"]);
|
|
git(["-c", "user.name=Fixture", "-c", "[email protected]", "-c", "core.hooksPath=/dev/null", "commit", "-qm", "fixture"]);
|
|
const before = sourceSnapshot(root);
|
|
assert.equal(before.dirty, false);
|
|
writeFileSync(path.join(root, "output/ignored.png"), "new run");
|
|
assert.equal(compareSnapshots(before, sourceSnapshot(root)).repositoryUnchanged, true);
|
|
writeFileSync(image, "first overwrite"); const first = sourceSnapshot(root);
|
|
assert.equal(compareSnapshots(before, first).sourceUnchanged, true);
|
|
assert.equal(compareSnapshots(before, first).repositoryUnchanged, false);
|
|
assert.ok(first.repository.porcelainV1Z.includes("output/archive.png\0"));
|
|
writeFileSync(image, "second overwrite"); const second = sourceSnapshot(root);
|
|
assert.equal(first.dirty, true); assert.equal(second.dirty, true);
|
|
assert.equal(first.repository.porcelainV1Z, second.repository.porcelainV1Z);
|
|
assert.equal(compareSnapshots(first, second).repositoryUnchanged, false);
|
|
git(["add", "-f", "output/archive.png"]); const staged = sourceSnapshot(root);
|
|
assert.equal(second.repository.trackedDigest, staged.repository.trackedDigest);
|
|
assert.notEqual(second.repository.indexDigest, staged.repository.indexDigest);
|
|
assert.equal(compareSnapshots(second, staged).repositoryUnchanged, false);
|
|
git(["update-index", "--assume-unchanged", "output/archive.png"]);
|
|
const flagged = sourceSnapshot(root);
|
|
assert.equal(staged.repository.trackedDigest, flagged.repository.trackedDigest);
|
|
assert.notEqual(staged.repository.indexDigest, flagged.repository.indexDigest);
|
|
assert.equal(compareSnapshots(staged, flagged).repositoryUnchanged, false);
|
|
git(["update-index", "--no-assume-unchanged", "output/archive.png"]);
|
|
unlinkSync(image); const deleted = sourceSnapshot(root);
|
|
assert.equal(deleted.repository.trackedFiles.find(entry => entry.file === "output/archive.png").kind, "missing");
|
|
assert.equal(compareSnapshots(staged, deleted).repositoryUnchanged, false);
|
|
symlinkSync("absent-target", image); const linked = sourceSnapshot(root);
|
|
assert.equal(linked.repository.trackedFiles.find(entry => entry.file === "output/archive.png").kind, "symlink");
|
|
assert.equal(compareSnapshots(deleted, linked).repositoryUnchanged, false);
|
|
assert.equal(compareSnapshots(null, linked).repositoryUnchanged, false);
|
|
assert.equal(compareSnapshots(null, linked).candidateIntegrityStatus, "UNKNOWN");
|
|
const incomplete = structuredClone(linked);
|
|
delete incomplete.repository.trackedDigest;
|
|
assert.equal(compareSnapshots(linked, incomplete).candidateIntegrityStatus, "UNKNOWN");
|
|
});
|
|
|
|
test("诊断临时根不污染原生产身份测试的合成合法路径", context => {
|
|
const scratch = createDiagnosticScratch();
|
|
context.after(() => rmSync(scratch, { recursive: true }));
|
|
const identity = {
|
|
deployServer: "production-node",
|
|
composeProjectName: "production-project",
|
|
remoteDir: path.join(scratch, "server-unit", "hyb-oa-deploy-safety-fixture", "app"),
|
|
};
|
|
const validate = remoteDir => validateExactProductionIdentity({
|
|
...identity,
|
|
remoteDir,
|
|
expectedDeployServer: identity.deployServer,
|
|
expectedComposeProjectName: identity.composeProjectName,
|
|
expectedRemoteDir: remoteDir,
|
|
});
|
|
assert.doesNotThrow(() => validate(identity.remoteDir));
|
|
assert.throws(() => validate(path.join(scratch, "hyb-full-local-diagnostic-fixture", "app")), /REMOTE_DIR_NON_PRODUCTION_IDENTITY/);
|
|
});
|
|
|
|
test("原聚合器在 workflow 失败后停止,同库继续参数会破坏该边界", context => {
|
|
const scratch = createDiagnosticScratch();
|
|
context.after(() => rmSync(scratch, { recursive: true }));
|
|
mkdirSync(path.join(scratch, "lib"));
|
|
copyFileSync(new URL("../../../server/scripts/regression-all.sh", import.meta.url), path.join(scratch, "regression-all.sh"));
|
|
copyFileSync(new URL("../../../server/scripts/lib/regression-report.sh", import.meta.url), path.join(scratch, "lib/regression-report.sh"));
|
|
const suites = ["smoke-test", "regression-dedup", "regression-workflow-actions", "regression-procurement", "regression-assets", "regression-audit", "regression-ocr", "regression-im-notifier"];
|
|
for (const suite of suites) {
|
|
writeFileSync(path.join(scratch, `${suite}.sh`), `#!/usr/bin/env bash\nprintf '%s\\n' '${suite}' >> "$QA_AGGREGATE_FIXTURE_LOG"\nexit ${suite === "regression-workflow-actions" ? 31 : 0}\n`, { mode: 0o755 });
|
|
}
|
|
const log = path.join(scratch, "invocations.log");
|
|
const environment = { PATH: process.env.PATH, HOME: scratch, TMPDIR: scratch, QA_AGGREGATE_FIXTURE_LOG: log };
|
|
const run = args => spawnSync("bash", [path.join(scratch, "regression-all.sh"), ...args], { cwd: scratch, env: environment, encoding: "utf8", timeout: 15000 });
|
|
const normal = run([]);
|
|
assert.equal(normal.status, 31, normal.stderr);
|
|
assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), suites.slice(0, 3));
|
|
writeFileSync(log, "");
|
|
const continued = run(["--json-report", path.join(scratch, "continued.json")]);
|
|
assert.equal(continued.status, 1, continued.stderr);
|
|
assert.deepEqual(readFileSync(log, "utf8").trim().split("\n"), suites);
|
|
});
|
|
|
|
const discovery = { cases: [
|
|
{ file: "client/tests/e2e/ordinary.spec.ts", id: "first", projectName: "", repeatEachIndex: 0 },
|
|
{ file: "client/tests/e2e/ordinary.spec.ts", id: "second", projectName: "", repeatEachIndex: 0 },
|
|
] };
|
|
|
|
test("缺浏览器回执不能从枚举伪造执行通过", () => {
|
|
const result = browserInventory(discovery, null);
|
|
assert.equal(result.reportPresent, false);
|
|
assert.deepEqual(result.cases.map(entry => entry.status), ["NOT_RUN", "NOT_RUN"]);
|
|
});
|
|
|
|
test("保留失败、serial 跳过、预期状态及重试事实", () => {
|
|
const execution = { errors: [], suites: [{ specs: [
|
|
{ id: "first", tests: [{ projectName: "", expectedStatus: "passed", status: "unexpected", results: [{ status: "failed", retry: 0, duration: 100 }] }] },
|
|
{ id: "second", tests: [{ projectName: "", expectedStatus: "passed", status: "skipped", results: [{ status: "skipped", retry: 0, duration: 0 }] }] },
|
|
] }] };
|
|
const result = browserInventory(discovery, execution);
|
|
assert.deepEqual(result.cases.map(entry => entry.status), ["failed", "skipped"]);
|
|
assert.equal(result.cases[1].reportedStatus, "skipped");
|
|
assert.equal(result.cases[0].results[0].retry, 0);
|
|
});
|
|
|
|
test("部分回执只为实际返回的用例计结果", () => {
|
|
const result = browserInventory(discovery, { errors: [{}], suites: [{ specs: [{ id: "first", tests: [{ results: [{ status: "passed" }] }] }] }] });
|
|
assert.deepEqual(result.cases.map(entry => entry.status), ["passed", "NOT_RUN"]);
|
|
assert.equal(result.executionErrors, 1);
|
|
});
|
|
|
|
test("重复和未知用例作为报告问题单列", () => {
|
|
const result = browserInventory(discovery, { suites: [{ specs: [
|
|
{ id: "unknown", tests: [{}, {}] },
|
|
] }] });
|
|
assert.deepEqual(result.issues, ["duplicate_case", "unknown_cases"]);
|
|
assert.ok(result.cases.every(entry => entry.status === "NOT_RUN"));
|
|
});
|