211 lines
6.5 KiB
TypeScript
211 lines
6.5 KiB
TypeScript
import { chmodSync, mkdirSync, mkdtempSync, realpathSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { loadConfigFromSources } from "./config.js";
|
|
import { DevflowError } from "./errors.js";
|
|
|
|
describe("config loader", () => {
|
|
it("loads .env, .env.local, then process env in descending precedence", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
writeFileSync(
|
|
join(root, ".env"),
|
|
[
|
|
"DATABASE_URL=postgres://env:env@localhost:5432/env",
|
|
"WORKSPACE_ROOT=workspace",
|
|
"LOG_LEVEL=warn",
|
|
"TEMPORAL_ADDRESS=localhost:7233",
|
|
].join("\n"),
|
|
);
|
|
writeFileSync(join(root, ".env.local"), "LOG_LEVEL=debug\n");
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://process:process@localhost:5432/process",
|
|
},
|
|
});
|
|
|
|
expect(config.DATABASE_URL).toBe("postgres://process:process@localhost:5432/process");
|
|
expect(config.LOG_LEVEL).toBe("debug");
|
|
expect(config.WORKSPACE_ROOT).toBe(realpathSync(workspace));
|
|
});
|
|
|
|
it("always exposes the fake backend as enabled", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
},
|
|
});
|
|
|
|
expect(config.backends).toContainEqual({ id: "fake", enabled: true });
|
|
expect(config.SESSION_MAX_HUNG_MS).toBe(20 * 60 * 1000);
|
|
});
|
|
|
|
it("loads configurable session hung timeout", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
SESSION_MAX_HUNG_MS: "2500",
|
|
},
|
|
});
|
|
|
|
expect(config.SESSION_MAX_HUNG_MS).toBe(2500);
|
|
});
|
|
|
|
it("resolves backend binaries from PATH during config load", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
const binDir = join(root, "bin");
|
|
const codexBin = join(binDir, "codex");
|
|
mkdirSync(workspace);
|
|
mkdirSync(binDir);
|
|
writeFileSync(codexBin, "#!/bin/sh\nexit 0\n");
|
|
chmodSync(codexBin, 0o755);
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
PATH: binDir,
|
|
DEVFLOW_BACKENDS_JSON: JSON.stringify([{ id: "codex", enabled: true }]),
|
|
},
|
|
});
|
|
|
|
expect(config.backends).toEqual([
|
|
{ id: "fake", enabled: true },
|
|
{ id: "codex", enabled: true, binaryPath: realpathSync(codexBin) },
|
|
]);
|
|
});
|
|
|
|
it("keeps enabled real backends unavailable when their binary cannot be resolved", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
const emptyBin = join(root, "empty-bin");
|
|
mkdirSync(workspace);
|
|
mkdirSync(emptyBin);
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
PATH: emptyBin,
|
|
DEVFLOW_BACKENDS_JSON: JSON.stringify([{ id: "codex", enabled: true }]),
|
|
},
|
|
});
|
|
|
|
expect(config.backends).toEqual([
|
|
{ id: "fake", enabled: true },
|
|
{ id: "codex", enabled: true },
|
|
]);
|
|
});
|
|
|
|
it("requires LOG_LEVEL and classifies invalid config as fatal", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
let caught: unknown;
|
|
try {
|
|
loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
caught = error;
|
|
}
|
|
|
|
expect(caught).toBeInstanceOf(DevflowError);
|
|
expect((caught as DevflowError).class).toBe("fatal");
|
|
expect((caught as DevflowError).code).toBe("config_invalid");
|
|
expect((caught as DevflowError).cause).toBeDefined();
|
|
});
|
|
|
|
it("requires TEMPORAL_ADDRESS at M5", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
expect(() =>
|
|
loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
},
|
|
}),
|
|
).toThrow(DevflowError);
|
|
});
|
|
|
|
it("classifies malformed backend JSON as invalid config", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
expect(() =>
|
|
loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
DEVFLOW_BACKENDS_JSON: "{",
|
|
},
|
|
}),
|
|
).toThrow(DevflowError);
|
|
});
|
|
|
|
it("freezes config and backend registrations", () => {
|
|
const root = mkdtempSync(join(tmpdir(), "devflow-config-"));
|
|
const workspace = join(root, "workspace");
|
|
mkdirSync(workspace);
|
|
|
|
const config = loadConfigFromSources({
|
|
cwd: root,
|
|
env: {
|
|
DATABASE_URL: "postgres://devflow:devflow@localhost:5432/devflow",
|
|
WORKSPACE_ROOT: workspace,
|
|
LOG_LEVEL: "info",
|
|
TEMPORAL_ADDRESS: "localhost:7233",
|
|
},
|
|
});
|
|
|
|
expect(Object.isFrozen(config)).toBe(true);
|
|
expect(Object.isFrozen(config.backends)).toBe(true);
|
|
expect(Object.isFrozen(config.backends[0])).toBe(true);
|
|
expect(() => {
|
|
(config.backends[0] as { enabled: boolean }).enabled = false;
|
|
}).toThrow(TypeError);
|
|
});
|
|
});
|