mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 12:43:13 +00:00
* feat(terraform): ship dev content CD through Terraform (SH-300) GitHub uploads immutable release prefixes; Terraform owns live publish. Push-to-dev stays off until TERRAFORM_CONTENT_CD_ENABLED is set. * fix(terraform): align release-plan guard flags and CloudFront verify IAM (SH-300)
179 lines
6.5 KiB
JavaScript
179 lines
6.5 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawnSync } from "node:child_process";
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { test } from "node:test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import {
|
|
OVERRIDE_LABEL,
|
|
classifyChangedFiles,
|
|
isTerraformInfrastructurePath,
|
|
mayAccompanyTerraform,
|
|
} from "./check-terraform-isolation.mjs";
|
|
|
|
const SCRIPT = path.join(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
"check-terraform-isolation.mjs",
|
|
);
|
|
|
|
function runGate(files, env = {}) {
|
|
return spawnSync(process.execPath, [SCRIPT, "--stdin"], {
|
|
input: `${files.join("\n")}\n`,
|
|
encoding: "utf8",
|
|
env: { ...process.env, TERRAFORM_ISOLATION_OVERRIDE: "", ...env },
|
|
});
|
|
}
|
|
|
|
test("terraform tree, docs, and terraform tooling may accompany a Terraform change", () => {
|
|
for (const file of [
|
|
"terraform/live/dev/main.tf",
|
|
"terraform/live/modules/environment-owned/main.tf",
|
|
"terraform/README.md",
|
|
"README.md",
|
|
"docs/adr/0003-terraform.md",
|
|
"scripts/check-terraform-import-plan.py",
|
|
"scripts/terraform_import_plan_resources.py",
|
|
"scripts/test-terraform-import-plan-check.py",
|
|
"scripts/terraform-validate.mjs",
|
|
"scripts/check-terraform-isolation.mjs",
|
|
"scripts/check-terraform-release-plan.py",
|
|
"scripts/hcp-run-guard.py",
|
|
"scripts/test-hcp-run-guard.py",
|
|
"scripts/verify-cloudfront-release.sh",
|
|
"scripts/test-verify-cloudfront-release.sh",
|
|
"scripts/summarize-cloudfront-live-state.sh",
|
|
"scripts/check-github-workflows.sh",
|
|
"scripts/read-release-pointer.py",
|
|
"scripts/testdata/terraform-release-plans/version-only.json",
|
|
]) {
|
|
assert.equal(mayAccompanyTerraform(file), true, file);
|
|
}
|
|
});
|
|
|
|
test("application, workflow, and dependency files count as application changes", () => {
|
|
for (const file of [
|
|
"src/App.tsx",
|
|
"public/favicon.ico",
|
|
"index.html",
|
|
"package.json",
|
|
"package-lock.json",
|
|
".env.production",
|
|
"vite.config.ts",
|
|
".github/workflows/deploy.yml",
|
|
"scripts/deploy-web.sh",
|
|
"scripts/governance-check.mjs",
|
|
"e2e/login.spec.ts",
|
|
]) {
|
|
assert.equal(mayAccompanyTerraform(file), false, file);
|
|
}
|
|
});
|
|
|
|
test("terraform-only and application-only changes are not mixed", () => {
|
|
assert.equal(
|
|
classifyChangedFiles(["terraform/live/dev/main.tf", "terraform/README.md"]).mixed,
|
|
false,
|
|
);
|
|
assert.equal(
|
|
classifyChangedFiles(["src/App.tsx", ".github/workflows/deploy.yml", "README.md"]).mixed,
|
|
false,
|
|
);
|
|
assert.equal(classifyChangedFiles([]).mixed, false);
|
|
});
|
|
|
|
test("terraform documentation does not mix with application or workflow changes", () => {
|
|
assert.equal(isTerraformInfrastructurePath("terraform/README.md"), false);
|
|
assert.equal(isTerraformInfrastructurePath("terraform/live/dev/main.tf"), true);
|
|
assert.equal(
|
|
classifyChangedFiles(["terraform/README.md", ".github/workflows/ci.yaml"]).mixed,
|
|
false,
|
|
);
|
|
const docsOnly = runGate(["terraform/README.md", ".github/workflows/ci.yaml"]);
|
|
assert.equal(docsOnly.status, 0, docsOnly.stdout + docsOnly.stderr);
|
|
assert.match(docsOnly.stdout, /PASS/);
|
|
});
|
|
|
|
test("terraform plus application is mixed and lists the offending files", () => {
|
|
const result = classifyChangedFiles([
|
|
"terraform/live/dev/main.tf",
|
|
"src/App.tsx",
|
|
"README.md",
|
|
" ",
|
|
"src/App.tsx",
|
|
]);
|
|
assert.equal(result.mixed, true);
|
|
assert.deepEqual(result.terraform, ["terraform/live/dev/main.tf"]);
|
|
assert.deepEqual(result.application, ["src/App.tsx"]);
|
|
});
|
|
|
|
test("CLI exits 1 on a mixed change and 0 when isolated", () => {
|
|
const mixed = runGate(["terraform/live/dev/main.tf", "src/App.tsx"]);
|
|
assert.equal(mixed.status, 1, mixed.stdout + mixed.stderr);
|
|
assert.match(mixed.stdout, /FAIL/);
|
|
assert.match(mixed.stdout, /src\/App\.tsx/);
|
|
|
|
const isolated = runGate(["terraform/live/dev/main.tf", "terraform/README.md"]);
|
|
assert.equal(isolated.status, 0, isolated.stdout + isolated.stderr);
|
|
assert.match(isolated.stdout, /PASS/);
|
|
});
|
|
|
|
test("CLI override downgrades a mixed change to a warning that names the label", () => {
|
|
const result = runGate(["terraform/live/dev/main.tf", "src/App.tsx"], {
|
|
TERRAFORM_ISOLATION_OVERRIDE: "true",
|
|
});
|
|
assert.equal(result.status, 0, result.stdout + result.stderr);
|
|
assert.match(result.stdout, /WARNING/);
|
|
assert.match(result.stdout, new RegExp(OVERRIDE_LABEL));
|
|
|
|
const notTrue = runGate(["terraform/live/dev/main.tf", "src/App.tsx"], {
|
|
TERRAFORM_ISOLATION_OVERRIDE: "yes",
|
|
});
|
|
assert.equal(notTrue.status, 1);
|
|
});
|
|
|
|
test("removing the override fails a mixed change that was previously green", () => {
|
|
const files = ["terraform/live/dev/main.tf", ".github/workflows/deploy.yml"];
|
|
const previouslyGreen = runGate(files, {
|
|
TERRAFORM_ISOLATION_OVERRIDE: "true",
|
|
});
|
|
assert.equal(previouslyGreen.status, 0, previouslyGreen.stdout + previouslyGreen.stderr);
|
|
assert.match(previouslyGreen.stdout, /WARNING/);
|
|
|
|
// CI sets TERRAFORM_ISOLATION_OVERRIDE from contains(...labels), which is
|
|
// the string "false" after the label is removed. A stale green check must
|
|
// not survive that.
|
|
const afterLabelRemoved = runGate(files, {
|
|
TERRAFORM_ISOLATION_OVERRIDE: "false",
|
|
});
|
|
assert.equal(afterLabelRemoved.status, 1, afterLabelRemoved.stdout + afterLabelRemoved.stderr);
|
|
assert.match(afterLabelRemoved.stdout, /FAIL/);
|
|
assert.match(afterLabelRemoved.stdout, /deploy\.yml/);
|
|
});
|
|
|
|
test("CLI refuses to run without a base ref or --stdin", () => {
|
|
const result = spawnSync(process.execPath, [SCRIPT], { encoding: "utf8" });
|
|
assert.notEqual(result.status, 0);
|
|
});
|
|
|
|
test("isolation workflow re-evaluates on labeled and unlabeled without rerunning Frontend checks", () => {
|
|
const workflows = path.join(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
"..",
|
|
".github/workflows",
|
|
);
|
|
const ciYaml = readFileSync(path.join(workflows, "ci.yaml"), "utf8");
|
|
const isolationYaml = readFileSync(path.join(workflows, "terraform-isolation.yaml"), "utf8");
|
|
|
|
for (const eventType of ["opened", "synchronize", "reopened", "labeled", "unlabeled"]) {
|
|
assert.match(isolationYaml, new RegExp(`^ {6}- ${eventType}$`, "m"), eventType);
|
|
}
|
|
|
|
assert.doesNotMatch(ciYaml, /^ {6}- labeled$/m);
|
|
assert.doesNotMatch(ciYaml, /^ {6}- unlabeled$/m);
|
|
assert.doesNotMatch(ciYaml, /^ {2}terraform-isolation:\n/m);
|
|
assert.doesNotMatch(ciYaml, /github\.event\.action != 'labeled'/);
|
|
|
|
assert.match(isolationYaml, /^ {2}terraform-isolation:\n/m);
|
|
assert.match(isolationYaml, /name: Terraform and application changes are isolated/);
|
|
assert.doesNotMatch(isolationYaml, /github\.event\.action != 'labeled'/);
|
|
});
|