Files
harness-tools/extensions/workflow/config.test.ts
T

240 lines
7.7 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import * as path from "node:path";
import test from "node:test";
import { loadWorkflowConfig } from "./config.ts";
import { DEFAULT_WORKFLOW_CONFIG, MAX_CONCURRENCY } from "./domain.ts";
async function withTempDir(run: (directory: string) => Promise<void>) {
const directory = await mkdtemp(path.join(tmpdir(), "workflow-config-"));
try {
await run(directory);
} finally {
await rm(directory, { recursive: true, force: true });
}
}
async function writeJson(file: string, value: unknown) {
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, `${JSON.stringify(value)}\n`);
}
test("configuration defaults to concurrency 8 with immutable ceiling 32", async () => {
await withTempDir(async (directory) => {
const config = await loadWorkflowConfig({
cwd: directory,
agentDir: path.join(directory, "agent"),
projectTrusted: false,
});
assert.deepEqual(config, DEFAULT_WORKFLOW_CONFIG);
assert.equal(config.concurrency.ceiling, MAX_CONCURRENCY);
assert.deepEqual(config.standardAgents, {
enabled: true,
roles: {
scout: ["discover"],
worker: ["implement"],
},
});
});
});
test("a lowered ceiling clamps an inherited default", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { ceiling: 4 },
});
const globalOnly = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: false,
});
assert.deepEqual(globalOnly.concurrency, { default: 4, ceiling: 4 });
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { default: 6, ceiling: 16 },
});
await writeJson(path.join(directory, ".pi", "workflow.json"), {
concurrency: { ceiling: 3 },
});
const project = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: true,
});
assert.deepEqual(project.concurrency, { default: 3, ceiling: 3 });
});
});
test("trusted project configuration layers over global configuration and may lower limits", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { default: 6, ceiling: 16 },
maxAssignmentsPerRun: 64,
analysisTools: ["read", "grep", "find"],
worktrees: { root: ".worktrees/global" },
});
await writeJson(path.join(directory, ".pi", "workflow.json"), {
concurrency: { default: 4, ceiling: 8 },
maxReviewCycles: 5,
analysisTools: ["read", "find", "ls"],
worktrees: { root: ".worktrees/project" },
});
const config = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: true,
invocationConcurrency: 7,
});
assert.deepEqual(config.concurrency, { default: 7, ceiling: 8 });
assert.equal(config.maxAssignmentsPerRun, 64);
assert.equal(config.maxReviewCycles, 5);
assert.deepEqual(config.analysisTools, ["read", "find"]);
assert.equal(config.worktrees.root, path.normalize(".worktrees/project"));
});
});
test("untrusted project configuration is never applied", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(directory, ".pi", "workflow.json"), {
concurrency: { default: 1, ceiling: 1 },
unknownField: true,
});
const config = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: false,
});
assert.deepEqual(config.concurrency, { default: 8, ceiling: 32 });
});
});
test("trusted configuration can replace or disable standard-agent role overlays", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
standardAgents: {
roles: {
"codebase-analyzer": ["discover"],
worker: ["implement", "verify"],
},
},
});
let config = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: false,
});
assert.deepEqual(config.standardAgents.roles, {
"codebase-analyzer": ["discover"],
worker: ["implement", "verify"],
});
await writeJson(path.join(directory, ".pi", "workflow.json"), {
standardAgents: { enabled: false },
});
config = await loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: true,
});
assert.equal(config.standardAgents.enabled, false);
});
});
test("invalid standard-agent names and roles fail before a run starts", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
standardAgents: { roles: { Worker: ["implement"] } },
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/lowercase kebab case/,
);
await writeJson(path.join(agentDir, "workflow.json"), {
standardAgents: { roles: { worker: ["remediate"] } },
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/unsupported role remediate/,
);
});
});
test("configuration cannot raise ceilings or activate unaudited tools", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { ceiling: 33 },
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/between 1 and 32/,
);
await writeJson(path.join(agentDir, "workflow.json"), {
analysisTools: ["read", "bash"],
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/unsupported tools: bash/,
);
});
});
test("project configuration cannot raise a lowered global ceiling", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { default: 4, ceiling: 8 },
});
await writeJson(path.join(directory, ".pi", "workflow.json"), {
concurrency: { ceiling: 16 },
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: true }),
/cannot raise the current ceiling/,
);
});
});
test("invalid numeric and path values fail before a run starts", async () => {
await withTempDir(async (directory) => {
const agentDir = path.join(directory, "agent");
await writeJson(path.join(agentDir, "workflow.json"), {
maxAssignmentsPerRun: 1.5,
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/must be an integer/,
);
await writeJson(path.join(agentDir, "workflow.json"), {
worktrees: { root: "../outside" },
});
await assert.rejects(
loadWorkflowConfig({ cwd: directory, agentDir, projectTrusted: false }),
/must stay inside the repository/,
);
await writeJson(path.join(agentDir, "workflow.json"), {
concurrency: { default: 8, ceiling: 8 },
});
await assert.rejects(
loadWorkflowConfig({
cwd: directory,
agentDir,
projectTrusted: false,
invocationConcurrency: 9,
}),
/between 1 and 8/,
);
});
});