文件
hyb-oa/scripts/qa/tests/full-local-diagnostic-results.test.mjs
T

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"));
});