Files
upstream-atomic-crm/.claude/hooks/test/config.test.mjs
upstream-mirror 93ecc1a6f8
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

158 lines
4.5 KiB
JavaScript

// Tests for lib/config.mjs: the harness.config.json loader. It merges the file
// over the built-in defaults, validates the shape (fail-closed on malformed),
// and degrades to defaults when the file is missing.
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { afterEach, describe, test, expect } from "vitest";
import {
loadConfig,
clearConfigCache,
CONFIG_FILENAME,
isDeployEnabled,
deployGlobs,
pipelineRoles,
debounceRoles,
roleModel,
worktreeProvision,
prePrSteps,
} from "../lib/config.mjs";
const REPO_ROOT = join(
dirname(fileURLToPath(import.meta.url)),
"..",
"..",
"..",
);
const tmpRepos = [];
const makeRepo = (config) => {
const dir = mkdtempSync(join(tmpdir(), "harness-config-"));
tmpRepos.push(dir);
if (config !== undefined) {
writeFileSync(
join(dir, CONFIG_FILENAME),
typeof config === "string" ? config : JSON.stringify(config),
);
}
clearConfigCache();
return dir;
};
const VALID = {
name: "sample",
validation: {
steps: [
{ id: "typecheck", kind: "typecheck", command: "npm run typecheck" },
{
id: "unit",
kind: "unit",
runner: "vitest",
config: "vitest.config.ts",
projects: ["app"],
},
],
},
roles: {
developer: { model: "sonnet", pipeline: true, debounce: true },
planner: { model: "opus", pipeline: true, debounce: false },
},
deploy: { adapter: "supabase", relevantGlobs: ["supabase/**"] },
};
afterEach(() => {
while (tmpRepos.length)
rmSync(tmpRepos.pop(), { recursive: true, force: true });
clearConfigCache();
});
describe("config loader", () => {
test("loads a valid config and applies defaults for absent keys", () => {
const cfg = loadConfig(makeRepo(VALID));
expect(cfg.name).toBe("sample");
// default filled in when absent
expect(worktreeProvision(cfg)).toBe("npm-link");
expect(cfg.launcher.sessionDirEnv).toBe("CHAT_SESSION_DIR");
});
test("missing file degrades to defaults (no deploy capability)", () => {
const cfg = loadConfig(makeRepo(undefined));
expect(isDeployEnabled(cfg)).toBe(false);
expect(cfg.validation.steps).toEqual([]);
});
test("typed readers reflect the config", () => {
const cfg = loadConfig(makeRepo(VALID));
expect(isDeployEnabled(cfg)).toBe(true);
expect(deployGlobs(cfg)).toEqual(["supabase/**"]);
expect(pipelineRoles(cfg).sort()).toEqual(["developer", "planner"]);
expect(debounceRoles(cfg)).toEqual(["developer"]);
expect(roleModel(cfg, "planner")).toBe("opus");
});
test("malformed JSON throws a clear, prefixed error", () => {
expect(() => loadConfig(makeRepo("{ not json"))).toThrow(
new RegExp(`${CONFIG_FILENAME}: invalid JSON`),
);
});
test("validation.steps not an array is rejected", () => {
expect(() =>
loadConfig(makeRepo({ validation: { steps: {} }, roles: {} })),
).toThrow(/validation\.steps` must be an array/);
});
test("a role missing a model is rejected", () => {
expect(() =>
loadConfig(
makeRepo({ validation: { steps: [] }, roles: { developer: {} } }),
),
).toThrow(/roles\.developer needs/);
});
test("deploy present but without relevantGlobs is rejected", () => {
expect(() =>
loadConfig(
makeRepo({ validation: { steps: [] }, roles: {}, deploy: {} }),
),
).toThrow(/deploy\.relevantGlobs/);
});
test("prePrSteps takes typecheck/lint but excludes changedScoped steps", () => {
const cfg = loadConfig(
makeRepo({
validation: {
steps: [
{
id: "typecheck",
kind: "typecheck",
command: "npm run typecheck",
},
{
id: "lint",
kind: "lint",
command: "npx eslint",
changedScoped: true,
},
],
},
roles: {},
}),
);
// typecheck (whole-repo-safe) is on the human push path; the diff-scoped lint
// step is not — it needs a per-worktree base the human path lacks.
expect(prePrSteps(cfg).map((s) => s.id)).toEqual(["typecheck"]);
});
test("the committed harness.config.json is valid", () => {
clearConfigCache();
const cfg = loadConfig(REPO_ROOT);
expect(cfg.name).toBe("atomic-crm");
expect(isDeployEnabled(cfg)).toBe(true);
expect(roleModel(cfg, "quality-reviewer")).toBe("opus");
expect(pipelineRoles(cfg)).toContain("test-writer");
});
});