789 行
41 KiB
JavaScript
789 行
41 KiB
JavaScript
const { execFile, execFileSync } = require("node:child_process");
|
|
const { createHash } = require("node:crypto");
|
|
const { chmod, mkdtemp, mkdir, rm, symlink, writeFile, readFile } = require("node:fs/promises");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
const { pathToFileURL } = require("node:url");
|
|
const { promisify } = require("node:util");
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
const ROOT_DIR = path.resolve(__dirname, "../../../..");
|
|
const PREFLIGHT = path.join(ROOT_DIR, "deploy/trialrun/preflight-remote.sh");
|
|
const AGGREGATE_PREFLIGHT = path.join(ROOT_DIR, "deploy/trialrun/check-existing-deployment.sh");
|
|
const REFRESH_HELPER = path.join(ROOT_DIR, "scripts/refresh-deployment-contract.sh");
|
|
const REFRESH_INSTALLER = path.join(ROOT_DIR, "deploy/trialrun/install-deployment-contract-refresh.sh");
|
|
const PRODUCTION_PREFLIGHT = path.join(ROOT_DIR, "deploy/trialrun/production-preflight.mjs");
|
|
const PRODUCTION_PREFLIGHT_REMOTE = path.join(ROOT_DIR, "deploy/trialrun/production-preflight-remote.sh");
|
|
const PRODUCTION_IDENTITY_BOOTSTRAP = path.join(ROOT_DIR, "deploy/trialrun/production-identity-bootstrap.mjs");
|
|
const PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE = path.join(ROOT_DIR, "deploy/trialrun/production-identity-bootstrap-remote.sh");
|
|
|
|
describe("deployment safety contract", () => {
|
|
let fixtureRoot;
|
|
let remoteDir;
|
|
let envFile;
|
|
let postgresDir;
|
|
let storageDir;
|
|
|
|
beforeEach(async () => {
|
|
fixtureRoot = await mkdtemp(path.join(os.tmpdir(), "hyb-oa-deploy-safety-"));
|
|
remoteDir = path.join(fixtureRoot, "app");
|
|
envFile = path.join(remoteDir, "deploy/trialrun/.env");
|
|
postgresDir = path.join(fixtureRoot, "postgres");
|
|
storageDir = path.join(fixtureRoot, "storage");
|
|
await mkdir(path.join(remoteDir, "server"), { recursive: true });
|
|
await mkdir(path.join(remoteDir, "deploy/trialrun"), { recursive: true });
|
|
await mkdir(postgresDir, { recursive: true });
|
|
await mkdir(storageDir, { recursive: true });
|
|
await writeFile(path.join(remoteDir, "server/package.json"), "{}\n");
|
|
await writeFile(path.join(remoteDir, "deploy/trialrun/docker-compose.yml"), "services: {}\n");
|
|
await writeFile(envFile, "APP_ORIGIN=https://example.invalid\n", { mode: 0o600 });
|
|
await writeFile(path.join(postgresDir, "PG_VERSION"), "16\n");
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(fixtureRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
function preflight(overrides = {}) {
|
|
return execFileAsync("bash", [PREFLIGHT], {
|
|
env: {
|
|
...process.env,
|
|
REMOTE_DIR: remoteDir,
|
|
REMOTE_ENV_FILE: envFile,
|
|
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
|
POSTGRES_DATA_DIR: postgresDir,
|
|
STORAGE_HOST_DIR: storageDir,
|
|
DEPLOY_DATA_MODE: "existing",
|
|
SYNC_ENV: "0",
|
|
...overrides,
|
|
},
|
|
});
|
|
}
|
|
|
|
function productionPreflightEnv({ fakeBin, reportDir, overrides = {} }) {
|
|
const candidateCommit = execFileSync("git", ["rev-parse", "HEAD"], {
|
|
cwd: ROOT_DIR,
|
|
encoding: "utf8",
|
|
}).trim();
|
|
return {
|
|
...process.env,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`,
|
|
DEPLOY_SERVER: "deploy.production.invalid",
|
|
REMOTE_DIR: remoteDir,
|
|
REMOTE_ENV_FILE: envFile,
|
|
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
|
COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
POSTGRES_DATA_DIR: postgresDir,
|
|
STORAGE_HOST_DIR: storageDir,
|
|
EXPECTED_PRODUCTION_ORIGIN: "https://oa.hyinda.com",
|
|
EXPECTED_PRODUCTION_DB_NAME_SHA256: createHash("sha256").update("hyb_oa", "utf8").digest("hex"),
|
|
EXPECTED_PENDING_MIGRATIONS: "",
|
|
EXPECTED_DEPLOY_SERVER: "deploy.production.invalid",
|
|
EXPECTED_REMOTE_DIR: remoteDir,
|
|
EXPECTED_COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
CANDIDATE_COMMIT: candidateCommit,
|
|
PRODUCTION_PREFLIGHT_REPORT_DIR: reportDir,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
it("accepts a complete existing deployment contract", async () => {
|
|
await expect(preflight()).resolves.toMatchObject({ stdout: expect.stringContaining("[remote-preflight][PASS]") });
|
|
});
|
|
|
|
it("rejects a config directory masquerading as the repository root", async () => {
|
|
await expect(preflight({ REMOTE_DIR: path.join(remoteDir, "deploy/trialrun") })).rejects.toMatchObject({ code: 1 });
|
|
});
|
|
|
|
it("rejects a missing or symbolic remote env before synchronization", async () => {
|
|
await rm(envFile);
|
|
await expect(preflight()).rejects.toMatchObject({ code: 1 });
|
|
await symlink(path.join(remoteDir, "missing.env"), envFile);
|
|
await expect(preflight()).rejects.toMatchObject({ code: 1 });
|
|
});
|
|
|
|
it("rejects invalid existing data directories", async () => {
|
|
await rm(path.join(postgresDir, "PG_VERSION"));
|
|
await expect(preflight()).rejects.toMatchObject({ code: 1 });
|
|
});
|
|
|
|
it("runs remote preflight before rsync and reads existing contracts from systemd", async () => {
|
|
const deploy = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "deploy/trialrun/deploy.sh"), "utf8");
|
|
const wrapper = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "deploy/trialrun/deploy-existing.sh"), "utf8");
|
|
expect(deploy.indexOf("REMOTE_PREFLIGHT_SCRIPT")).toBeGreaterThanOrEqual(0);
|
|
expect(deploy.indexOf('>>> 3. 同步前只读校验远端部署合同')).toBeLessThan(deploy.indexOf("rsync -az --delete"));
|
|
expect(deploy.indexOf('>>> 3. 同步前只读校验远端部署合同')).toBeLessThan(deploy.indexOf("mkdir -p '${REMOTE_DIR}'"));
|
|
expect(wrapper).toContain("check-existing-deployment.sh");
|
|
expect(wrapper.indexOf("check-existing-deployment.sh")).toBeLessThan(wrapper.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"'));
|
|
expect(wrapper).not.toContain("docker inspect");
|
|
expect(wrapper).not.toContain("working_dir");
|
|
expect(wrapper).toContain("DEPLOY_DATA_MODE=existing");
|
|
expect(wrapper).toContain("SYNC_ENV=0");
|
|
expect(wrapper).toContain("DEPLOY_DEFER_FINAL_SUCCESS=1");
|
|
expect(wrapper).toContain("DEPLOY_STATUS_OUTPUT_MODE=quiet");
|
|
expect(deploy).toContain("DEPLOY_STATUS_OUTPUT_MODE");
|
|
expect(deploy).toContain("DEPLOY_STATUS_OUTPUT_MODE='${DEPLOY_STATUS_OUTPUT_MODE}'");
|
|
expect(deploy).toContain('DEPLOY_RUNTIME_LOG_REF="artifacts/deployment-runtime"');
|
|
expect(deploy).toContain('install -d -m 0700 "$DEPLOY_RUNTIME_LOG_DIR"');
|
|
expect(deploy).toContain('chmod 0600 "$log_file"');
|
|
expect(deploy).toContain('>"$log_file" 2>&1');
|
|
expect(deploy).toMatch(/if "\$@" <\/dev\/null >"\$log_file" 2>&1; then[\s\S]*else\s+local status=\$\?/u);
|
|
expect(deploy).toContain('run_logged_deploy_stage "build-init"');
|
|
expect(deploy).toContain('run_logged_deploy_stage "run-init"');
|
|
expect(deploy).toContain('run_logged_deploy_stage "build-switch-server"');
|
|
expect(deploy).not.toContain('\n"${compose_cmd[@]}" build init\n');
|
|
expect(deploy).not.toContain('\n"${compose_cmd[@]}" run --rm -T --entrypoint sh init');
|
|
expect(deploy).not.toContain('\n"${compose_cmd[@]}" "${profile_args[@]}" up -d --build server\n');
|
|
expect(wrapper).toContain("生产部署验证不接受未进入上游的提交");
|
|
expect(wrapper).toContain("部署后 migration status 与目标只读复核");
|
|
expect(wrapper).toContain('EXPECTED_PENDING_MIGRATIONS=""');
|
|
expect(wrapper.indexOf('EXPECTED_PENDING_MIGRATIONS=""')).toBeGreaterThan(
|
|
wrapper.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"'),
|
|
);
|
|
expect(wrapper.indexOf('ssh "$DEPLOY_REFRESH_SERVER" refresh')).toBeGreaterThan(wrapper.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"'));
|
|
expect(wrapper).toContain("DEPLOYED_BUT_CONTRACT_REFRESH_FAILED");
|
|
});
|
|
|
|
it("requires production target identity and pending migration allowlist before deploy.sh", async () => {
|
|
const wrapper = await readFile(path.join(ROOT_DIR, "deploy/trialrun/deploy-existing.sh"), "utf8");
|
|
const deploy = await readFile(path.join(ROOT_DIR, "deploy/trialrun/deploy.sh"), "utf8");
|
|
const preflight = await readFile(PRODUCTION_PREFLIGHT, "utf8");
|
|
const remote = await readFile(PRODUCTION_PREFLIGHT_REMOTE, "utf8");
|
|
const bootstrap = await readFile(PRODUCTION_IDENTITY_BOOTSTRAP, "utf8");
|
|
|
|
expect(wrapper).toContain("EXPECTED_PRODUCTION_ORIGIN");
|
|
expect(wrapper).toContain("EXPECTED_PRODUCTION_DB_NAME_SHA256");
|
|
expect(wrapper).toContain("EXPECTED_PENDING_MIGRATIONS");
|
|
expect(wrapper).toContain("EXPECTED_DEPLOY_SERVER");
|
|
expect(wrapper).toContain("EXPECTED_REMOTE_DIR");
|
|
expect(wrapper).toContain("EXPECTED_COMPOSE_PROJECT_NAME");
|
|
expect(wrapper).toContain("PRODUCTION_READ_ONLY_PREFLIGHT");
|
|
expect(wrapper.indexOf('node "$ROOT_DIR/$PRODUCTION_PREFLIGHT_SCRIPT"')).toBeLessThan(
|
|
wrapper.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"'),
|
|
);
|
|
expect(wrapper.indexOf('if [[ "${PRODUCTION_READ_ONLY_PREFLIGHT:-0}" == "1" ]]')).toBeLessThan(
|
|
wrapper.indexOf('bash "$ROOT_DIR/deploy/trialrun/deploy.sh"'),
|
|
);
|
|
expect(preflight).toContain("CANDIDATE_MIGRATIONS_CSV");
|
|
expect(preflight).toContain("PRISMA_STATUS_OUTPUT_MISSING");
|
|
expect(preflight).toContain("PRISMA_STATUS_OUTPUT_UNREADABLE");
|
|
expect(preflight).toContain("CANDIDATE_COMMIT_MISSING");
|
|
expect(preflight).not.toContain("rev-parse");
|
|
expect(preflight).toContain("deriveCandidateBuildEnv");
|
|
expect(preflight).toContain("Object.entries(candidateBuildEnv)");
|
|
expect(preflight).not.toContain("CANDIDATE_BUILD_ENV_KEYS");
|
|
expect(preflight).not.toContain("candidateCommit.slice(0, 12)");
|
|
expect(deploy).toContain("Object.entries(p.buildEnv ?? {})");
|
|
expect(deploy).toContain("declare -A SOURCE_BUILD_ENV=()");
|
|
expect(deploy).toContain('"${#SOURCE_BUILD_ENV[@]}" -eq 2');
|
|
expect(deploy).not.toContain("p.buildEnv.BUILD_SHA");
|
|
expect(deploy).not.toContain("p.buildEnv.VITE_BUILD_SHA");
|
|
expect(deploy).not.toContain("p.shortCommit");
|
|
expect(deploy).not.toContain("${BUILD_SHA:0:12}");
|
|
expect(deploy).toContain('${VITE_BUILD_SHA}.log');
|
|
expect(deploy).toContain('-e EXPECTED_BUILD_SHA="${VITE_BUILD_SHA}"');
|
|
expect(preflight).toContain("redacted datasource line");
|
|
expect(preflight).toContain("PRODUCTION_PREFLIGHT_REPORT_NOT_FRESH");
|
|
expect(preflight.lastIndexOf("printStableRemoteSummary(stdout)")).toBeLessThan(
|
|
preflight.lastIndexOf("assertRemotePreflightResult({"),
|
|
);
|
|
expect(preflight.indexOf("reportRef=${reportRef(reportDir)}")).toBeLessThan(
|
|
preflight.lastIndexOf("assertRemotePreflightResult({"),
|
|
);
|
|
expect(preflight.lastIndexOf("assertRemotePreflightResult({")).toBeLessThan(
|
|
preflight.lastIndexOf("validatePrismaStatusText(rawStatus);"),
|
|
);
|
|
expect(remote).toContain("PRISMA_STATUS_BEGIN");
|
|
expect(remote).toContain("PROD_MIGRATION_STATUS_UNKNOWN_PENDING");
|
|
expect(remote).toContain("logged_failure");
|
|
expect(remote).toContain("PROD_DATABASE_URL_HOST_NON_PRODUCTION_IDENTITY");
|
|
expect(remote).toContain("PROD_TARGET_BUILD_SHA_MISMATCH");
|
|
expect(remote).toContain('FROM "_prisma_migrations"');
|
|
expect(remote).toContain("rollbackWindow=OPEN");
|
|
expect(remote).not.toMatch(/\b(prisma migrate reset|prisma db push|prisma migrate resolve)\b/);
|
|
expect(remote.indexOf("PRISMA_STATUS_BEGIN")).toBeLessThan(remote.indexOf("_prisma_migrations"));
|
|
expect(bootstrap.indexOf("assertContractForOriginVerification")).toBeLessThan(
|
|
bootstrap.indexOf("verifyOriginIdentity(identity, contract, expectedOrigin);"),
|
|
);
|
|
expect(bootstrap.indexOf("verifyOriginIdentity(identity, contract, expectedOrigin);")).toBeLessThan(
|
|
bootstrap.indexOf("const expectedRemoteDir = contract.REMOTE_DIR"),
|
|
);
|
|
expect(bootstrap.indexOf("const expectedRemoteDir = contract.REMOTE_DIR")).toBeLessThan(
|
|
bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash"),
|
|
);
|
|
expect(bootstrap.indexOf('if (identityOnly === "1")')).toBeLessThan(
|
|
bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash"),
|
|
);
|
|
expect(bootstrap.indexOf("const databaseIdentityHash = bootstrapDatabaseIdentityHash")).toBeLessThan(
|
|
bootstrap.indexOf("spawnSync(\"bash\", [DEPLOY_EXISTING_SCRIPT]"),
|
|
);
|
|
});
|
|
|
|
it("rejects loopback/staging production identities without invoking SSH", async () => {
|
|
const target = await import(pathToFileURL(PRODUCTION_PREFLIGHT).href);
|
|
expect(() => target.validateProductionOrigin("https://127.0.0.1")).toThrowError(
|
|
expect.objectContaining({ code: "PROD_TARGET_ORIGIN_NON_PRODUCTION_IDENTITY" }),
|
|
);
|
|
expect(() => target.validateProductionOrigin("https://staging.example.com")).toThrowError(
|
|
expect.objectContaining({ code: "PROD_TARGET_ORIGIN_NON_PRODUCTION_IDENTITY" }),
|
|
);
|
|
expect(() => target.validateProductionOrigin("https://0.0.0.0")).toThrowError(
|
|
expect.objectContaining({ code: "PROD_TARGET_ORIGIN_NON_PRODUCTION_IDENTITY" }),
|
|
);
|
|
expect(() => target.validateProductionOrigin("https://[::ffff:127.0.0.1]")).toThrowError(
|
|
expect.objectContaining({ code: "PROD_TARGET_ORIGIN_NON_PRODUCTION_IDENTITY" }),
|
|
);
|
|
expect(() => target.validateProductionDbName("hyb_oa_regression")).toThrowError(
|
|
expect.objectContaining({ code: "PROD_TARGET_DB_NON_PRODUCTION_IDENTITY" }),
|
|
);
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.production.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "deploy.production.invalid",
|
|
expectedRemoteDir: "/srv/hyb-oa-trialrun",
|
|
expectedComposeProjectName: "hyb-oa-trialrun",
|
|
})).not.toThrow();
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.production.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "deploy.production.invalid",
|
|
expectedRemoteDir: "/srv/different",
|
|
expectedComposeProjectName: "hyb-oa-trialrun",
|
|
})).toThrowError(expect.objectContaining({ code: "REMOTE_DIR_MISMATCH" }));
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.production.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "different.production.invalid",
|
|
expectedRemoteDir: "/srv/hyb-oa-trialrun",
|
|
expectedComposeProjectName: "hyb-oa-trialrun",
|
|
})).toThrowError(expect.objectContaining({ code: "DEPLOY_SERVER_MISMATCH" }));
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.production.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "deploy.production.invalid",
|
|
expectedRemoteDir: "/srv/hyb-oa-trialrun",
|
|
expectedComposeProjectName: "different",
|
|
})).toThrowError(expect.objectContaining({ code: "COMPOSE_PROJECT_NAME_MISMATCH" }));
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.production.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "deploy.production.invalid",
|
|
expectedRemoteDir: "/srv/hyb-oa-trialrun",
|
|
})).toThrowError(expect.objectContaining({ code: "EXPECTED_COMPOSE_PROJECT_NAME_MISSING" }));
|
|
expect(() => target.validateExactProductionIdentity({
|
|
deployServer: "deploy.staging.invalid",
|
|
remoteDir: "/srv/hyb-oa-trialrun",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
expectedDeployServer: "deploy.staging.invalid",
|
|
expectedRemoteDir: "/srv/hyb-oa-trialrun",
|
|
expectedComposeProjectName: "hyb-oa-trialrun",
|
|
})).toThrowError(expect.objectContaining({ code: "DEPLOY_SERVER_NON_PRODUCTION_IDENTITY" }));
|
|
expect(() => target.validatePrismaStatusText("")).toThrowError(
|
|
expect.objectContaining({ code: "PRISMA_STATUS_OUTPUT_UNREADABLE" }),
|
|
);
|
|
expect(() => target.validatePrismaStatusText("unexpected output")).toThrowError(
|
|
expect.objectContaining({ code: "PRISMA_STATUS_OUTPUT_UNREADABLE" }),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "prefers a remote safety code when the marker is missing",
|
|
status: 1,
|
|
stdout: "",
|
|
stderr: "[production-preflight][PAUSED] code=PROD_DATABASE_IDENTITY_MISMATCH",
|
|
rawStatus: null,
|
|
expectedCode: "PROD_DATABASE_IDENTITY_MISMATCH",
|
|
},
|
|
{
|
|
name: "prefers a remote safety code when the marker exists",
|
|
status: 1,
|
|
stdout: "[production-preflight][PAUSED] code=PROD_MIGRATION_STATUS_FAILED",
|
|
stderr: "",
|
|
rawStatus: "Database schema is up to date!",
|
|
expectedCode: "PROD_MIGRATION_STATUS_FAILED",
|
|
},
|
|
{
|
|
name: "fails closed when a remote failure has no parseable code",
|
|
status: 1,
|
|
stdout: "remote command failed",
|
|
stderr: "ssh exited unexpectedly",
|
|
rawStatus: null,
|
|
expectedCode: "PRODUCTION_PREFLIGHT_REMOTE_FAILED",
|
|
},
|
|
{
|
|
name: "requires the Prisma marker after a successful remote exit",
|
|
status: 0,
|
|
stdout: "[production-preflight][PASS] target_identity",
|
|
stderr: "",
|
|
rawStatus: null,
|
|
expectedCode: "PRISMA_STATUS_OUTPUT_MISSING",
|
|
},
|
|
])("$name", async ({ status, stdout, stderr, rawStatus, expectedCode }) => {
|
|
const target = await import(pathToFileURL(PRODUCTION_PREFLIGHT).href);
|
|
expect(() => target.assertRemotePreflightResult({ status, stdout, stderr, rawStatus })).toThrowError(
|
|
expect.objectContaining({ code: expectedCode }),
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
name: "propagates a remote code when the marker is missing",
|
|
mode: "code-no-marker",
|
|
expectedCode: "PROD_DATABASE_IDENTITY_MISMATCH",
|
|
},
|
|
{
|
|
name: "propagates a remote code when the marker is present",
|
|
mode: "code-with-marker",
|
|
expectedCode: "PROD_MIGRATION_STATUS_FAILED",
|
|
},
|
|
{
|
|
name: "propagates a remote code when the marker body is unreadable",
|
|
mode: "code-with-unreadable-marker",
|
|
expectedCode: "PROD_MIGRATION_STATUS_FAILED",
|
|
},
|
|
{
|
|
name: "uses the fallback for a remote failure without a code",
|
|
mode: "no-code-no-marker",
|
|
expectedCode: "PRODUCTION_PREFLIGHT_REMOTE_FAILED",
|
|
},
|
|
{
|
|
name: "requires the marker after a successful remote exit",
|
|
mode: "success-no-marker",
|
|
expectedCode: "PRISMA_STATUS_OUTPUT_MISSING",
|
|
},
|
|
])("runs the real preflight subprocess and $name", async ({ mode, expectedCode }) => {
|
|
const fakeBin = path.join(fixtureRoot, `preflight-bin-${mode}`);
|
|
const reportDir = path.join(fixtureRoot, `preflight-report-${mode}`);
|
|
await mkdir(fakeBin, { recursive: true });
|
|
const fakeSsh = path.join(fakeBin, "ssh");
|
|
await writeFile(fakeSsh, [
|
|
"#!/usr/bin/env node",
|
|
"const mode = process.env.FAKE_PREFLIGHT_SSH_MODE;",
|
|
"if (mode === 'code-no-marker') {",
|
|
" process.stdout.write('[production-preflight][PAUSED] code=PROD_DATABASE_IDENTITY_MISMATCH\\n');",
|
|
" process.exit(41);",
|
|
"}",
|
|
"if (mode === 'code-with-marker') {",
|
|
" process.stdout.write('[production-preflight][PRISMA_STATUS_BEGIN]\\n');",
|
|
" process.stdout.write('Database schema is up to date!\\n');",
|
|
" process.stdout.write('[production-preflight][PRISMA_STATUS_END] exit=0\\n');",
|
|
" process.stdout.write('[production-preflight][PAUSED] code=PROD_MIGRATION_STATUS_FAILED\\n');",
|
|
" process.exit(42);",
|
|
"}",
|
|
"if (mode === 'code-with-unreadable-marker') {",
|
|
" process.stdout.write('[production-preflight][PRISMA_STATUS_BEGIN]\\n');",
|
|
" process.stdout.write('truncated or unknown prisma output\\n');",
|
|
" process.stdout.write('[production-preflight][PRISMA_STATUS_END] exit=1\\n');",
|
|
" process.stdout.write('[production-preflight][PAUSED] code=PROD_MIGRATION_STATUS_FAILED\\n');",
|
|
" process.exit(44);",
|
|
"}",
|
|
"if (mode === 'no-code-no-marker') {",
|
|
" process.stdout.write('remote command failed\\n');",
|
|
" process.stderr.write('ssh exited unexpectedly\\n');",
|
|
" process.exit(43);",
|
|
"}",
|
|
"if (mode === 'success-no-marker') {",
|
|
" process.stdout.write('[production-preflight][PASS] target_identity\\n');",
|
|
" process.exit(0);",
|
|
"}",
|
|
"process.stderr.write('unexpected fake ssh mode\\n');",
|
|
"process.exit(97);",
|
|
"",
|
|
].join("\n"), { mode: 0o700 });
|
|
await chmod(fakeSsh, 0o700);
|
|
|
|
let failure;
|
|
try {
|
|
await execFileAsync("node", [PRODUCTION_PREFLIGHT], {
|
|
cwd: ROOT_DIR,
|
|
env: productionPreflightEnv({
|
|
fakeBin,
|
|
reportDir,
|
|
overrides: { FAKE_PREFLIGHT_SSH_MODE: mode },
|
|
}),
|
|
});
|
|
} catch (error) {
|
|
failure = error;
|
|
}
|
|
expect(failure).toBeDefined();
|
|
expect(failure.code).toBe(1);
|
|
expect(failure.stderr).toContain(`[production-preflight][PAUSED] code=${expectedCode}`);
|
|
expect(failure.stdout).toContain("[production-preflight] reportRef=");
|
|
const stableEvidence = await readFile(path.join(reportDir, "preflight-output.txt"), "utf8");
|
|
expect(stableEvidence.trim()).not.toBe("");
|
|
expect(failure.stdout).toContain(stableEvidence.trim());
|
|
});
|
|
|
|
it("captures a redacted pre-status evidence copy from a controlled SSH fixture", async () => {
|
|
const fakeBin = path.join(fixtureRoot, "bin");
|
|
const reportDir = path.join(fixtureRoot, "report");
|
|
const sshCapture = path.join(fixtureRoot, "ssh-command.txt");
|
|
await mkdir(fakeBin, { recursive: true });
|
|
await mkdir(reportDir, { recursive: true });
|
|
await writeFile(path.join(reportDir, "prisma-migrate-status.txt"), "stale PASS\n");
|
|
const fakeSsh = path.join(fakeBin, "ssh");
|
|
await writeFile(
|
|
fakeSsh,
|
|
[
|
|
"#!/usr/bin/env node",
|
|
"require('node:fs').writeFileSync(process.env.FAKE_SSH_CAPTURE, process.argv.slice(2).join(' '));",
|
|
"process.stdout.write('[production-preflight][PASS] target_identity\\n');",
|
|
"process.stdout.write('[production-preflight][PRISMA_STATUS_BEGIN]\\n');",
|
|
"process.stdout.write('Datasource \\\"db\\\": PostgreSQL database \\\"hyb_oa\\\" at \\\"db.internal:5432\\\"\\n');",
|
|
"process.stdout.write('Database schema is up to date!\\n');",
|
|
"process.stdout.write('[production-preflight][PRISMA_STATUS_END] exit=0\\n');",
|
|
"process.stdout.write('[production-preflight][PASS] migration_reconciliation pending=none\\n');",
|
|
"process.stdout.write('[production-preflight][PASS] rollbackWindow=OPEN terminatedRows=0\\n');",
|
|
].join("\n"),
|
|
{ mode: 0o700 },
|
|
);
|
|
await chmod(fakeSsh, 0o700);
|
|
|
|
const candidateCommit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: ROOT_DIR, encoding: "utf8" }).trim();
|
|
const provenance = await import(pathToFileURL(path.join(ROOT_DIR, "deploy/trialrun/check-source-provenance.mjs")).href);
|
|
const candidateBuildEnv = provenance.deriveCandidateBuildEnv(candidateCommit);
|
|
const result = await execFileAsync("node", [PRODUCTION_PREFLIGHT], {
|
|
cwd: ROOT_DIR,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`,
|
|
DEPLOY_SERVER: "deploy.production.invalid",
|
|
REMOTE_DIR: remoteDir,
|
|
REMOTE_ENV_FILE: envFile,
|
|
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
|
COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
POSTGRES_DATA_DIR: postgresDir,
|
|
STORAGE_HOST_DIR: storageDir,
|
|
EXPECTED_PRODUCTION_ORIGIN: "https://oa.hyinda.com",
|
|
EXPECTED_PRODUCTION_DB_NAME_SHA256: createHash("sha256").update("hyb_oa", "utf8").digest("hex"),
|
|
EXPECTED_PENDING_MIGRATIONS: "",
|
|
EXPECTED_DEPLOY_SERVER: "deploy.production.invalid",
|
|
EXPECTED_REMOTE_DIR: remoteDir,
|
|
EXPECTED_COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
CANDIDATE_COMMIT: candidateCommit,
|
|
PRODUCTION_PREFLIGHT_REPORT_DIR: reportDir,
|
|
FAKE_SSH_CAPTURE: sshCapture,
|
|
},
|
|
});
|
|
const statusEvidence = await readFile(path.join(reportDir, "prisma-migrate-status.txt"), "utf8");
|
|
const sshCommand = await readFile(sshCapture, "utf8");
|
|
expect(result.stdout).toContain("[production-preflight][PASS]");
|
|
expect(statusEvidence).toContain("Database schema is up to date!");
|
|
expect(statusEvidence).toContain("[redacted datasource line]");
|
|
expect(statusEvidence).not.toContain("hyb_oa");
|
|
expect(statusEvidence).not.toContain("db.internal:5432");
|
|
expect(result.stdout).not.toContain("oa.hyinda.com");
|
|
expect(result.stdout).not.toContain("hyb_oa");
|
|
expect(sshCommand).not.toContain("oa.hyinda.com");
|
|
expect(sshCommand).not.toContain("hyb_oa");
|
|
expect(sshCommand).toContain("EXPECTED_PRODUCTION_ORIGIN_SHA256");
|
|
expect(sshCommand).toContain("EXPECTED_PRODUCTION_DB_NAME_SHA256");
|
|
expect(sshCommand).toContain(`BUILD_SHA='${candidateBuildEnv.BUILD_SHA}'`);
|
|
expect(sshCommand).toContain(`VITE_BUILD_SHA='${candidateBuildEnv.VITE_BUILD_SHA}'`);
|
|
});
|
|
|
|
it("bootstraps only an exact historical identity and a unique refresh probe", async () => {
|
|
const bootstrap = await import(pathToFileURL(PRODUCTION_IDENTITY_BOOTSTRAP).href);
|
|
const historicalTarget = [
|
|
'DEPLOY_SERVER="${DEPLOY_SERVER:-deploy.production.invalid}"',
|
|
'REMOTE_DIR="${REMOTE_DIR:-/srv/hyb-oa-trialrun}"',
|
|
].join("\n");
|
|
const historicalProject = [
|
|
'REMOTE_DIR="${REMOTE_DIR:-/srv/hyb-oa-trialrun}"',
|
|
'COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-hyb-oa-trialrun}"',
|
|
].join("\n");
|
|
expect(bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject)).toEqual({
|
|
deployServer: "deploy.production.invalid",
|
|
composeProjectName: "hyb-oa-trialrun",
|
|
});
|
|
expect(bootstrap.selectUniqueRefreshTarget([
|
|
{ target: "ordinary", status: 1, stdout: "" },
|
|
{ target: "refresh", status: 0, stdout: "[deployment-contract-refresh][PASS] probe\n" },
|
|
])).toBe("refresh");
|
|
expect(() => bootstrap.selectUniqueRefreshTarget([])).toThrowError(
|
|
expect.objectContaining({ code: "REFRESH_IDENTITY_NOT_FOUND" }),
|
|
);
|
|
expect(() => bootstrap.selectUniqueRefreshTarget([
|
|
{ target: "refresh-a", status: 0, stdout: "[deployment-contract-refresh][PASS] probe" },
|
|
{ target: "refresh-b", status: 0, stdout: "[deployment-contract-refresh][PASS] probe" },
|
|
])).toThrowError(expect.objectContaining({ code: "REFRESH_IDENTITY_NOT_UNIQUE" }));
|
|
|
|
const contract = bootstrap.parseDeploymentContract([
|
|
"REMOTE_DIR=/srv/hyb-oa-trialrun",
|
|
"REMOTE_ENV_FILE=/srv/hyb-oa-trialrun/deploy/trialrun/.env",
|
|
"COMPOSE_FILE=deploy/trialrun/docker-compose.yml",
|
|
"COMPOSE_PROJECT_NAME=hyb-oa-trialrun",
|
|
"POSTGRES_DATA_DIR=/srv/data/postgres",
|
|
"STORAGE_HOST_DIR=/srv/data/storage",
|
|
].join(" "));
|
|
expect(() => bootstrap.assertContractForOriginVerification(
|
|
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
|
contract,
|
|
)).not.toThrow();
|
|
const relocatedContract = {
|
|
...contract,
|
|
REMOTE_DIR: "/srv/current-production",
|
|
REMOTE_ENV_FILE: "/srv/current-production/deploy/trialrun/.env",
|
|
};
|
|
expect(() => bootstrap.assertContractForOriginVerification(
|
|
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
|
relocatedContract,
|
|
)).not.toThrow();
|
|
expect(() => bootstrap.assertContractForOriginVerification(
|
|
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
|
{ ...contract, COMPOSE_PROJECT_NAME: "different" },
|
|
)).toThrowError(expect.objectContaining({ code: "SYSTEMD_COMPOSE_PROJECT_MISMATCH" }));
|
|
expect(() => bootstrap.assertContractForOriginVerification(
|
|
bootstrap.parseHistoricalDeploymentIdentity(historicalTarget, historicalProject),
|
|
{ ...contract, REMOTE_ENV_FILE: "/srv/different/.env" },
|
|
)).toThrowError(expect.objectContaining({ code: "SYSTEMD_REMOTE_ENV_FILE_MISMATCH" }));
|
|
|
|
const bootstrapSource = await readFile(PRODUCTION_IDENTITY_BOOTSTRAP, "utf8");
|
|
const remoteSource = await readFile(PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE, "utf8");
|
|
expect(bootstrapSource).not.toContain("console.log(databaseIdentityHash)");
|
|
expect(bootstrapSource).not.toContain("console.log(identity)");
|
|
expect(remoteSource).toContain("DB_SHA256");
|
|
expect(remoteSource).not.toContain("DB_BASE64");
|
|
expect(remoteSource).not.toContain("prisma migrate status");
|
|
expect(remoteSource.indexOf('IDENTITY_BOOTSTRAP_MODE" == "origin"')).toBeLessThan(
|
|
remoteSource.indexOf("actual_db_name="),
|
|
);
|
|
});
|
|
|
|
it("verifies both origin samples before reading the remote database name", async () => {
|
|
const fakeBin = path.join(fixtureRoot, "origin-bin");
|
|
await mkdir(fakeBin, { recursive: true });
|
|
await writeFile(path.join(fakeBin, "docker"), [
|
|
"#!/usr/bin/env bash",
|
|
"set -euo pipefail",
|
|
"joined=\"$*\"",
|
|
"if [[ \"$1\" == compose && \"${2:-}\" == version ]]; then exit 0; fi",
|
|
"if [[ \"$1\" == ps ]]; then printf 'server1\\n'; exit 0; fi",
|
|
"if [[ \"$1\" == inspect ]]; then printf 'APP_ORIGIN=https://example.invalid\\n'; exit 0; fi",
|
|
"printf 'unexpected fake docker call: %s\\n' \"$joined\" >&2",
|
|
"exit 97",
|
|
"",
|
|
].join("\n"), { mode: 0o700 });
|
|
await chmod(path.join(fakeBin, "docker"), 0o700);
|
|
const hash = createHash("sha256").update("https://example.invalid", "utf8").digest("hex");
|
|
const runBootstrapRemote = (mode) => execFileAsync("bash", [PRODUCTION_IDENTITY_BOOTSTRAP_REMOTE], {
|
|
cwd: remoteDir,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`,
|
|
IDENTITY_BOOTSTRAP_MODE: mode,
|
|
REMOTE_DIR: remoteDir,
|
|
REMOTE_ENV_FILE: envFile,
|
|
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
|
COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
EXPECTED_PRODUCTION_ORIGIN_SHA256: hash,
|
|
},
|
|
});
|
|
await expect(runBootstrapRemote("origin")).resolves.toMatchObject({
|
|
stdout: "[production-identity-bootstrap][PASS] origin_identity\n",
|
|
});
|
|
await expect(runBootstrapRemote("database")).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TARGET_DB_NAME_MISSING"),
|
|
});
|
|
});
|
|
|
|
it("runs the remote gate against synthetic status and migration history", async () => {
|
|
const fakeBin = path.join(fixtureRoot, "remote-bin");
|
|
await mkdir(fakeBin, { recursive: true });
|
|
await writeFile(path.join(remoteDir, "deploy/trialrun/.env"), [
|
|
"APP_ORIGIN=https://oa.example.com",
|
|
"POSTGRES_DB=production_db",
|
|
"POSTGRES_USER=synthetic_user",
|
|
"NODE_ENV=production",
|
|
"COOKIE_SECURE=true",
|
|
"",
|
|
].join("\n"), { mode: 0o600 });
|
|
await writeFile(path.join(fakeBin, "docker"), [
|
|
"#!/usr/bin/env bash",
|
|
"set -euo pipefail",
|
|
"joined=\"$*\"",
|
|
"mode=\"${FAKE_REMOTE_MODE:-happy}\"",
|
|
"if [[ -n \"${FAKE_DOCKER_TRACE:-}\" ]]; then printf '%s\\n' \"$joined\" >> \"$FAKE_DOCKER_TRACE\"; fi",
|
|
"if [[ \"$1\" == compose && \"${2:-}\" == version ]]; then exit 0; fi",
|
|
"if [[ \"$1\" == ps ]]; then",
|
|
" if [[ \"$joined\" == *service=server* ]]; then printf 'server1\\n'; else printf 'db1\\n'; fi",
|
|
" exit 0",
|
|
"fi",
|
|
"if [[ \"$1\" == inspect ]]; then printf 'NODE_ENV=production\\nCOOKIE_SECURE=true\\nAPP_ORIGIN=https://oa.example.com\\n'; exit 0; fi",
|
|
"if [[ \"$joined\" == *' config'* ]]; then [[ \"$mode\" == compose-invalid ]] && exit 91; exit 0; fi",
|
|
"if [[ \"$joined\" == *' exec '* && \"$joined\" == *' sh -lc '* ]]; then",
|
|
" case \"$mode\" in",
|
|
" pending) printf 'Following migration have not yet been applied:\\n20260807193001_followup_migration\\n'; exit 1 ;;",
|
|
" unknown) printf 'Following migration have not yet been applied:\\n20260807193002_unknown_migration\\n'; exit 1 ;;",
|
|
" *) printf 'Database schema is up to date!\\n'; exit 0 ;;",
|
|
" esac",
|
|
"fi",
|
|
"if [[ \"$joined\" == *' exec '* && \"$joined\" == *' node -e '* ]]; then",
|
|
" printf 'db.internal\\nproduction_db\\n'; exit 0",
|
|
"fi",
|
|
"if [[ \"$joined\" == *'SELECT current_database()'* ]]; then printf 'production_db\\n'; exit 0; fi",
|
|
"if [[ \"$joined\" == *'_prisma_migrations'* ]]; then",
|
|
" case \"$mode\" in",
|
|
" pending) printf '20260807193000_contract_registry_termination_workspace|applied\\n';;",
|
|
" termination-pending) printf '20260807193001_followup_migration|applied\\n';;",
|
|
" logs) printf '20260807193000_contract_registry_termination_workspace|logged_failure\\n';;",
|
|
" *) printf '20260807193000_contract_registry_termination_workspace|applied\\n20260807193001_followup_migration|applied\\n';;",
|
|
" esac",
|
|
" exit 0",
|
|
"fi",
|
|
"if [[ \"$joined\" == *'contract_registry'* ]]; then",
|
|
" case \"$mode\" in",
|
|
" termination-pending|termination-query-failure) exit 97 ;;",
|
|
" closed) printf '2\\n' ;;",
|
|
" *) printf '0\\n' ;;",
|
|
" esac",
|
|
" exit 0",
|
|
"fi",
|
|
"printf 'unexpected fake docker call: %s\\n' \"$joined\" >&2",
|
|
"exit 97",
|
|
"",
|
|
].join("\n"), { mode: 0o700 });
|
|
await chmod(path.join(fakeBin, "docker"), 0o700);
|
|
|
|
const hash = (value) => createHash("sha256").update(value, "utf8").digest("hex");
|
|
const buildSha = "a".repeat(40);
|
|
const dockerTrace = path.join(fixtureRoot, "fake-docker-calls.log");
|
|
const runRemote = (mode, expectedPending = "", buildOverrides = {}) => execFileAsync("bash", [PRODUCTION_PREFLIGHT_REMOTE], {
|
|
cwd: remoteDir,
|
|
env: {
|
|
...process.env,
|
|
PATH: `${fakeBin}${path.delimiter}${process.env.PATH}`,
|
|
FAKE_REMOTE_MODE: mode,
|
|
FAKE_DOCKER_TRACE: dockerTrace,
|
|
REMOTE_DIR: remoteDir,
|
|
REMOTE_ENV_FILE: envFile,
|
|
COMPOSE_FILE: "deploy/trialrun/docker-compose.yml",
|
|
COMPOSE_PROJECT_NAME: "hyb-oa-trialrun",
|
|
POSTGRES_DATA_DIR: postgresDir,
|
|
STORAGE_HOST_DIR: storageDir,
|
|
EXPECTED_PRODUCTION_ORIGIN_SHA256: hash("https://oa.example.com"),
|
|
EXPECTED_PRODUCTION_DB_NAME_SHA256: hash("production_db"),
|
|
BUILD_SHA: buildSha,
|
|
VITE_BUILD_SHA: buildSha.slice(0, 12),
|
|
CANDIDATE_MIGRATIONS_CSV: "20260807193000_contract_registry_termination_workspace,20260807193001_followup_migration",
|
|
EXPECTED_PENDING_MIGRATIONS_CSV: expectedPending,
|
|
...buildOverrides,
|
|
},
|
|
});
|
|
const result = await runRemote("happy");
|
|
expect(result.stdout).toContain("target_identity");
|
|
expect(result.stdout).toContain("migration_reconciliation pending=none");
|
|
expect(result.stdout).toContain("rollbackWindow=OPEN terminatedRows=0 basis=terminated_status_count");
|
|
const pendingResult = await runRemote("pending", "20260807193001_followup_migration");
|
|
expect(pendingResult.stdout).toContain("migration_reconciliation pending=20260807193001_followup_migration");
|
|
const terminationPendingResult = await runRemote(
|
|
"termination-pending",
|
|
"20260807193000_contract_registry_termination_workspace",
|
|
);
|
|
expect(terminationPendingResult.stdout).toContain(
|
|
"rollbackWindow=OPEN terminatedRows=0 basis=migration_not_applied",
|
|
);
|
|
const closedResult = await runRemote("closed");
|
|
expect(closedResult.stdout).toContain(
|
|
"rollbackWindow=CLOSED terminatedRows=2 basis=terminated_status_count",
|
|
);
|
|
await expect(runRemote("termination-query-failure")).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TERMINATED_COUNT_UNAVAILABLE"),
|
|
});
|
|
await expect(runRemote("unknown")).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_MIGRATION_STATUS_UNKNOWN_PENDING"),
|
|
});
|
|
await expect(runRemote("logs")).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_MIGRATION_FAILED_OR_ROLLED_BACK"),
|
|
});
|
|
await writeFile(dockerTrace, "");
|
|
await expect(runRemote("compose-invalid")).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TARGET_COMPOSE_CONFIG_INVALID"),
|
|
});
|
|
const composeFailureCalls = await readFile(dockerTrace, "utf8");
|
|
expect(composeFailureCalls).toContain(" config");
|
|
expect(composeFailureCalls).not.toContain("prisma migrate status");
|
|
expect(composeFailureCalls).not.toContain(" exec ");
|
|
await writeFile(dockerTrace, "");
|
|
await expect(runRemote("happy", "", { BUILD_SHA: "" })).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("BUILD_SHA_MISSING"),
|
|
});
|
|
expect(await readFile(dockerTrace, "utf8")).toBe("");
|
|
await expect(runRemote("happy", "", { BUILD_SHA: "a".repeat(39) })).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TARGET_BUILD_SHA_INVALID"),
|
|
});
|
|
await expect(runRemote("happy", "", { VITE_BUILD_SHA: "a".repeat(7) })).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TARGET_VITE_BUILD_SHA_INVALID"),
|
|
});
|
|
await expect(runRemote("happy", "", { VITE_BUILD_SHA: "b".repeat(12) })).rejects.toMatchObject({
|
|
stderr: expect.stringContaining("PROD_TARGET_BUILD_SHA_MISMATCH"),
|
|
});
|
|
});
|
|
|
|
it("aggregates preflight checks and keeps the root helper self-contained", async () => {
|
|
const aggregate = await require("node:fs/promises").readFile(AGGREGATE_PREFLIGHT, "utf8");
|
|
const helper = await require("node:fs/promises").readFile(REFRESH_HELPER, "utf8");
|
|
const installer = await require("node:fs/promises").readFile(REFRESH_INSTALLER, "utf8");
|
|
expect(aggregate).toContain("set -uo pipefail");
|
|
expect(aggregate).not.toMatch(/^set\s+-[A-Za-z]*e[A-Za-z]*(?:\s|$)/m);
|
|
expect(aggregate).toContain("CHECK_NAMES=(");
|
|
expect(aggregate).toContain("root_ssh_identity");
|
|
expect(aggregate).toContain("refresh_program");
|
|
expect(helper).toContain("SSH_ORIGINAL_COMMAND");
|
|
expect(helper).toContain("20-active-build.conf");
|
|
expect(helper).toContain("refresh_program_integrity_invalid");
|
|
expect(helper).not.toContain("eval ");
|
|
expect(helper).not.toContain("bash -c");
|
|
expect(helper).not.toContain("REMOTE_DIR/");
|
|
expect(installer).toContain("dedicated key already has unmanaged or unrestricted root authorization");
|
|
expect(installer).toContain("no-agent-forwarding,no-port-forwarding,no-X11-forwarding,no-pty,no-user-rc");
|
|
expect(installer).toContain("managed forced-command marker is malformed");
|
|
});
|
|
|
|
it("keeps immutable build args after stable dependency layers", async () => {
|
|
const dockerfile = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "deploy/trialrun/Dockerfile"), "utf8");
|
|
expect(dockerfile.indexOf("RUN apk add --no-cache bash curl")).toBeLessThan(dockerfile.indexOf("ARG VITE_BUILD_SHA"));
|
|
expect(dockerfile.indexOf("RUN cd client && npm ci")).toBeLessThan(dockerfile.indexOf("ARG VITE_BUILD_SHA"));
|
|
expect(dockerfile.indexOf("RUN cd server && npm ci")).toBeLessThan(dockerfile.indexOf("ARG BUILD_SHA"));
|
|
});
|
|
|
|
it("keeps deployment smoke output free of raw account and external identifiers", async () => {
|
|
const seed = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "server/src/scripts/seed-trialrun.js"), "utf8");
|
|
const external = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "server/src/scripts/smoke-external-deps.js"), "utf8");
|
|
const runtime = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "server/src/scripts/smoke-trialrun-runtime.js"), "utf8");
|
|
expect(seed).not.toContain("${adminEmail}");
|
|
expect(seed).not.toContain("${adminPhone}");
|
|
expect(seed).not.toContain("${result.password}");
|
|
expect(seed).not.toContain("generateOneTimePassword");
|
|
expect(external).not.toContain("corpId=${config.wecomCorpId}");
|
|
expect(external).not.toContain("redirectUri=${config.wecomRedirectUri}");
|
|
expect(external).not.toContain("from=${config.emailFrom}");
|
|
expect(runtime).not.toContain("${adminEmployee.employee_no}");
|
|
});
|
|
|
|
it("keeps the formal payment batch baseline enabled and restores QA caller state", async () => {
|
|
const envExample = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "deploy/trialrun/.env.example"), "utf8");
|
|
const runtime = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "server/src/scripts/smoke-trialrun-runtime.js"), "utf8");
|
|
const shortWindow = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "scripts/qa/run-p2-fin-live-window.sh"), "utf8");
|
|
const enableWindow = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "scripts/qa/run-fin-prod-enable-window.sh"), "utf8");
|
|
const liveSpec = await require("node:fs/promises").readFile(path.join(ROOT_DIR, "client/tests/e2e/manual/p2-fin-live-payment-batch.spec.ts"), "utf8");
|
|
|
|
expect(envExample).toMatch(/^PAYMENT_BATCH_ENABLED=true$/m);
|
|
expect(runtime).toContain("PAYMENT_BATCH_ENABLED: 'true'");
|
|
expect(runtime).toContain("FEATURE_FLAG_KEYS.paymentBatch");
|
|
expect(shortWindow).toContain("restore-caller-flag-snapshot");
|
|
expect(shortWindow).toContain("P2_FIN_LIVE_ALREADY_ON='$ALREADY_ON_MODE'");
|
|
expect(enableWindow).toContain("restore-caller-flag-snapshot");
|
|
expect(liveSpec).toContain("restoreFlagSnapshot");
|
|
expect(liveSpec).not.toContain("forcePaymentBatchOff");
|
|
expect(shortWindow).not.toContain("force_payment_batch_flag_off");
|
|
expect(enableWindow).not.toContain("force_payment_batch_flag_off");
|
|
});
|
|
});
|