Files
ForgeFlow/tests/git-validator.test.mjs
NuklearRabbit 258f0b1324
ForgeFlow quality gate / quality (push) Canceled after 0s
fix: restore scrolling and validator enforcement
2026-08-01 12:44:31 +02:00

143 lines
6.0 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, rm, writeFile, readFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { createRequire } from "node:module";
const exec = promisify(execFile);
const require = createRequire(import.meta.url);
const { GitService } = require("../src/main/git-service.cjs");
const {
GitValidatorService,
isSensitiveTrackedPath,
sameRemote,
} = require("../src/main/git-validator-service.cjs");
async function git(args, cwd) {
return exec("git", args, { cwd, encoding: "utf8" });
}
test("Git Validator scores repository hygiene and offers bounded safe repairs", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "forgeflow-validator-"));
t.after(() => rm(root, { recursive: true, force: true }));
await git(["init", "-b", "main"], root);
await git(["config", "user.name", "ForgeFlow Test"], root);
await git(["config", "user.email", "forgeflow@example.invalid"], root);
await git(
["remote", "add", "origin", "https://gitea.example.test/jens/app.git"],
root,
);
await writeFile(path.join(root, "README.md"), "# App\n", "utf8");
await writeFile(path.join(root, ".gitignore"), ".env\n", "utf8");
await git(["add", "."], root);
await git(["commit", "-m", "Initial"], root);
const validator = new GitValidatorService({
git: new GitService(),
gitea: {
getBranchProtection: async () => ({
protected: false,
enableForcePush: false,
}),
},
});
const repository = {
fullName: "jens/app",
name: "app",
owner: { login: "jens" },
defaultBranch: "main",
localPath: root,
cloneUrl: "https://gitea.example.test/jens/app.git",
sshUrl: "git@gitea.example.test:jens/app.git",
};
const report = await validator.scan(repository);
assert.ok(report.score > 60);
assert.equal(
report.checks.find((check) => check.id === "origin").status,
"pass",
);
assert.equal(
report.checks.find((check) => check.id === "default-branch-protection")
.fixAction,
"protect-default-branch",
);
const safety = report.checks.find((check) => check.id === "local-safety");
assert.equal(safety.safe, true);
await validator.repair(repository, safety);
const rescanned = await validator.scan(repository);
assert.equal(
rescanned.checks.find((check) => check.id === "local-safety").status,
"pass",
);
});
test("Git Validator creates a reviewable gitignore without committing it", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "forgeflow-ignore-"));
t.after(() => rm(root, { recursive: true, force: true }));
await git(["init", "-b", "main"], root);
const validator = new GitValidatorService({ git: new GitService() });
const repository = { localPath: root };
await validator.repair(repository, { fixAction: "add-gitignore" });
const content = await readFile(path.join(root, ".gitignore"), "utf8");
assert.match(content, /\.env/);
const status = await git(["status", "--short"], root);
assert.match(status.stdout, /\?\? \.gitignore/);
});
test("Git Validator recognizes remote aliases and secret-shaped tracked paths", () => {
assert.equal(
sameRemote(
"git@gitea.example.test:jens/app.git",
"https://gitea.example.test/jens/app",
),
true,
);
assert.equal(isSensitiveTrackedPath(".env.production"), true);
assert.equal(isSensitiveTrackedPath("config/private.pem"), true);
assert.equal(isSensitiveTrackedPath(".env.example"), false);
});
test("Git Validator rejects stale or forged repair requests", async () => {
const validator = new GitValidatorService({ git: new GitService() });
validator.scan = async () => ({
checks: [{ id: "local-safety", fixAction: "configure-local-safety", status: "warning" }],
});
assert.equal(
(await validator.resolveRepairCheck({}, { id: "local-safety", fixAction: "configure-local-safety" })).id,
"local-safety",
);
await assert.rejects(
validator.resolveRepairCheck({}, { id: "local-safety", fixAction: "align-origin" }),
/stale/i,
);
await assert.rejects(
validator.resolveRepairCheck({}, { id: "resolved-check", fixAction: "align-origin" }),
/resolved|no longer repairable/i,
);
});
test("Git Validator reports reproducibility, CI and editor hygiene and creates reviewable defaults", async (t) => {
const root = await mkdtemp(path.join(os.tmpdir(), "forgeflow-hygiene-"));
t.after(() => rm(root, { recursive: true, force: true }));
await git(["init", "-b", "main"], root);
await git(["config", "user.name", "ForgeFlow Test"], root);
await git(["config", "user.email", "forgeflow@example.invalid"], root);
await git(["remote", "add", "origin", "https://gitea.example.test/jens/app.git"], root);
await writeFile(path.join(root, "package.json"), '{"name":"app"}\n', "utf8");
await writeFile(path.join(root, "README.md"), "# App\n", "utf8");
await writeFile(path.join(root, ".gitignore"), ".env\n", "utf8");
await git(["add", "."], root);
await git(["commit", "-m", "Initial"], root);
const validator = new GitValidatorService({ git: new GitService(), gitea: { getBranchProtection: async () => ({ protected: true, enableForcePush: false }) } });
const repository = { fullName: "jens/app", name: "app", owner: { login: "jens" }, defaultBranch: "main", localPath: root, cloneUrl: "https://gitea.example.test/jens/app.git" };
const report = await validator.scan(repository);
assert.equal(report.checks.find((check) => check.id === "dependency-lock").status, "warning");
assert.equal(report.checks.find((check) => check.id === "continuous-integration").status, "warning");
for (const action of ["add-gitattributes", "add-editorconfig"])
await validator.repair(repository, { fixAction: action });
assert.match(await readFile(path.join(root, ".gitattributes"), "utf8"), /text=auto/);
assert.match(await readFile(path.join(root, ".editorconfig"), "utf8"), /root = true/);
});