Files
homarr/scripts/release-v2-qa/provenance.spec.ts
T

134 lines
5.2 KiB
TypeScript

// @vitest-environment node
import { spawnSync } from "node:child_process";
import { access, mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import Database from "better-sqlite3";
import { afterEach, describe, expect, test } from "vitest";
import { assertReleaseV2QaProfileEnvironment, releaseV2QaProfiles, validateCandidateSha } from "./provenance.mts";
import type { ReleaseV2QaProfile } from "./provenance.mts";
const disposableDirectories: string[] = [];
const repoRoot = path.resolve(import.meta.dirname, "../..");
const profileEnvironmentNames = ["DEMO_MODE", "DEMO_READ_ONLY", "UNSAFE_ENABLE_MOCK_INTEGRATION"] as const;
afterEach(async () => {
await Promise.all(
disposableDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })),
);
});
const environmentForProfile = (profile: ReleaseV2QaProfile): NodeJS.ProcessEnv => {
const environment: NodeJS.ProcessEnv = {
DEMO_MODE: "true",
DEMO_READ_ONLY: "false",
UNSAFE_ENABLE_MOCK_INTEGRATION: "true",
};
if (profile === "main-readonly") environment.DEMO_READ_ONLY = "true";
if (profile === "onboarding-fresh") environment.DEMO_MODE = "false";
return environment;
};
describe("release-v2 QA candidate provenance", () => {
test("accepts only the exact checked-out commit", () => {
const checkoutSha = "a".repeat(40);
expect(validateCandidateSha(checkoutSha.toUpperCase(), checkoutSha)).toBe(checkoutSha);
expect(() => validateCandidateSha("b".repeat(40), checkoutSha)).toThrow("checked-out HEAD");
expect(() => validateCandidateSha("not-a-sha", checkoutSha)).toThrow("full 40-character Git SHA");
});
});
describe("release-v2 QA profile provenance", () => {
test.each(releaseV2QaProfiles)("accepts the exact %s profile matrix", (profile) => {
expect(assertReleaseV2QaProfileEnvironment(profile, environmentForProfile(profile))).toEqual({
demoMode: profile !== "onboarding-fresh",
demoReadOnly: profile === "main-readonly",
unsafeMockIntegration: true,
});
});
test.each(profileEnvironmentNames)("rejects an unset %s flag", (environmentName) => {
const environment = environmentForProfile("main-writable");
delete environment[environmentName];
expect(() => assertReleaseV2QaProfileEnvironment("main-writable", environment)).toThrow(
new RegExp(`${environmentName} \\(expected (?:true|false)\\)`, "u"),
);
});
test.each(["1", "0", "TRUE", "False", "yes", " true ", "", "unexpected-private-marker"])(
"rejects the malformed strict boolean %j without echoing it",
(malformedValue) => {
for (const environmentName of profileEnvironmentNames) {
const environment = environmentForProfile("main-writable");
environment[environmentName] = malformedValue;
expect(() => assertReleaseV2QaProfileEnvironment("main-writable", environment)).toThrow(environmentName);
try {
assertReleaseV2QaProfileEnvironment("main-writable", environment);
} catch (error) {
expect((error as Error).message).not.toContain(malformedValue || "unexpected-private-marker");
}
}
},
);
test.each(releaseV2QaProfiles)("rejects every mismatched flag for %s", (profile) => {
const expectedEnvironment = environmentForProfile(profile);
for (const environmentName of profileEnvironmentNames) {
const environment = { ...expectedEnvironment };
environment[environmentName] = environment[environmentName] === "true" ? "false" : "true";
expect(() => assertReleaseV2QaProfileEnvironment(profile, environment)).toThrow(environmentName);
}
});
test("rejects a writable ambient environment before a read-only reset or manifest write", async () => {
const disposableDirectory = await mkdtemp(path.join(tmpdir(), "homarr-release-v2-qa-profile-flags-"));
disposableDirectories.push(disposableDirectory);
const databasePath = path.join(disposableDirectory, "fixture.sqlite");
const outputDirectory = path.join(disposableDirectory, "output");
const database = new Database(databasePath);
database.exec("CREATE TABLE mutation_guard (value TEXT NOT NULL); INSERT INTO mutation_guard VALUES ('preserved')");
database.close();
const databaseBefore = await readFile(databasePath);
const result = spawnSync(
process.execPath,
[
"--import",
"tsx",
path.join(repoRoot, "scripts/release-v2-qa/seed.mts"),
"--profile",
"main-readonly",
"--output",
outputDirectory,
"--reset",
],
{
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
DB_DRIVER: "better-sqlite3",
DB_URL: databasePath,
DEMO_MODE: "true",
DEMO_READ_ONLY: "false",
UNSAFE_ENABLE_MOCK_INTEGRATION: "true",
},
timeout: 30_000,
},
);
expect(result.error).toBeUndefined();
expect(result.status).toBe(1);
expect(result.stderr).toContain("DEMO_READ_ONLY (expected true)");
expect(result.stderr).not.toContain("no such table");
expect(await readFile(databasePath)).toEqual(databaseBefore);
await expect(access(path.join(outputDirectory, "fixture-manifest.json"))).rejects.toMatchObject({ code: "ENOENT" });
});
});