Cover high-priority untested modules: - util: base32, result, refs-field, storage-root, log-tag - util-agent: storage (normalizeWorkflowConfig, resolveStorageRoot), run (parseArgv) - agent-builtin: tools (read-file, write-file, run-command), session, detail 627 → 719 tests (+92), all passing. Refs #35
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { parseArgv } from "../src/run.js";
|
||||
|
||||
describe("parseArgv", () => {
|
||||
let exitSpy: ReturnType<typeof vi.spyOn>;
|
||||
let stderrSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
|
||||
throw new Error("process.exit");
|
||||
}) as never);
|
||||
stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation((() => true) as never);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("returns threadId, role, prompt for valid argv", () => {
|
||||
const result = parseArgv(["node", "script", "--thread", "abc123", "--role", "developer", "--prompt", "do stuff"]);
|
||||
expect(result).toEqual({ threadId: "abc123", role: "developer", prompt: "do stuff" });
|
||||
});
|
||||
|
||||
it("exits when --thread is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--role", "dev", "--prompt", "x"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("exits when --role is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--thread", "t1", "--prompt", "x"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("exits when --prompt is missing", () => {
|
||||
expect(() => parseArgv(["node", "script", "--thread", "t1", "--role", "dev"])).toThrow("process.exit");
|
||||
expect(exitSpy).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
import { homedir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import {
|
||||
resolveStorageRoot,
|
||||
getDefaultStorageRoot,
|
||||
getCasDir,
|
||||
getConfigPath,
|
||||
getEnvPath,
|
||||
getGlobalCasDir,
|
||||
normalizeWorkflowConfig,
|
||||
} from "../src/storage.js";
|
||||
|
||||
const VALID_CONFIG = {
|
||||
defaultAgent: "builtin",
|
||||
defaultModel: "main",
|
||||
providers: { openai: { baseUrl: "https://api.openai.com/v1", apiKey: "sk-xxx" } },
|
||||
models: { main: { provider: "openai", name: "gpt-4" } },
|
||||
agents: { builtin: { command: "uwf-builtin", args: ["--verbose"] } },
|
||||
};
|
||||
|
||||
describe("getDefaultStorageRoot", () => {
|
||||
it("returns homedir/.uwf", () => {
|
||||
expect(getDefaultStorageRoot()).toBe(join(homedir(), ".uwf"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveStorageRoot", () => {
|
||||
const saved: Record<string, string | undefined> = {};
|
||||
|
||||
beforeEach(() => {
|
||||
saved.UWF_STORAGE_ROOT = process.env.UWF_STORAGE_ROOT;
|
||||
saved.WORKFLOW_STORAGE_ROOT = process.env.WORKFLOW_STORAGE_ROOT;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const k of ["UWF_STORAGE_ROOT", "WORKFLOW_STORAGE_ROOT"] as const) {
|
||||
if (saved[k] === undefined) delete process.env[k];
|
||||
else process.env[k] = saved[k];
|
||||
}
|
||||
});
|
||||
|
||||
it("uses UWF_STORAGE_ROOT first", () => {
|
||||
process.env.UWF_STORAGE_ROOT = "/tmp/uwf1";
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf1");
|
||||
});
|
||||
|
||||
it("falls back to WORKFLOW_STORAGE_ROOT", () => {
|
||||
delete process.env.UWF_STORAGE_ROOT;
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf2");
|
||||
});
|
||||
|
||||
it("falls back to default when both unset", () => {
|
||||
delete process.env.UWF_STORAGE_ROOT;
|
||||
delete process.env.WORKFLOW_STORAGE_ROOT;
|
||||
expect(resolveStorageRoot()).toBe(getDefaultStorageRoot());
|
||||
});
|
||||
|
||||
it("ignores empty UWF_STORAGE_ROOT", () => {
|
||||
process.env.UWF_STORAGE_ROOT = "";
|
||||
process.env.WORKFLOW_STORAGE_ROOT = "/tmp/uwf2";
|
||||
expect(resolveStorageRoot()).toBe("/tmp/uwf2");
|
||||
});
|
||||
});
|
||||
|
||||
describe("path helpers", () => {
|
||||
it("getCasDir", () => expect(getCasDir("/root")).toBe("/root/cas"));
|
||||
it("getConfigPath", () => expect(getConfigPath("/root")).toBe("/root/config.yaml"));
|
||||
it("getEnvPath", () => expect(getEnvPath("/root")).toBe("/root/.env"));
|
||||
});
|
||||
|
||||
describe("getGlobalCasDir", () => {
|
||||
const saved = { OCAS_DIR: process.env.OCAS_DIR };
|
||||
|
||||
afterEach(() => {
|
||||
if (saved.OCAS_DIR === undefined) delete process.env.OCAS_DIR;
|
||||
else process.env.OCAS_DIR = saved.OCAS_DIR;
|
||||
});
|
||||
|
||||
it("uses OCAS_DIR when set", () => {
|
||||
process.env.OCAS_DIR = "/tmp/ocas";
|
||||
expect(getGlobalCasDir()).toBe("/tmp/ocas");
|
||||
});
|
||||
|
||||
it("defaults to ~/.ocas", () => {
|
||||
delete process.env.OCAS_DIR;
|
||||
expect(getGlobalCasDir()).toBe(join(homedir(), ".ocas"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeWorkflowConfig", () => {
|
||||
it("normalizes a valid config", () => {
|
||||
const result = normalizeWorkflowConfig(VALID_CONFIG);
|
||||
expect(result.defaultAgent).toBe("builtin");
|
||||
expect(result.defaultModel).toBe("main");
|
||||
expect(result.providers.openai.baseUrl).toBe("https://api.openai.com/v1");
|
||||
expect(result.models.main.name).toBe("gpt-4");
|
||||
expect(result.agents.builtin.command).toBe("uwf-builtin");
|
||||
expect(result.agents.builtin.args).toEqual(["--verbose"]);
|
||||
expect(result.modelOverrides).toBeNull();
|
||||
expect(result.agentOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("throws on non-record root", () => {
|
||||
expect(() => normalizeWorkflowConfig("bad")).toThrow("root must be a mapping");
|
||||
expect(() => normalizeWorkflowConfig(null)).toThrow("root must be a mapping");
|
||||
expect(() => normalizeWorkflowConfig([])).toThrow("root must be a mapping");
|
||||
});
|
||||
|
||||
it("throws when defaultAgent missing", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, defaultAgent: undefined }))
|
||||
.toThrow("defaultAgent and defaultModel");
|
||||
});
|
||||
|
||||
it("throws when defaultModel missing", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, defaultModel: 42 }))
|
||||
.toThrow("defaultAgent and defaultModel");
|
||||
});
|
||||
|
||||
it("throws on invalid providers entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, providers: { bad: "string" } }))
|
||||
.toThrow("config.providers.bad must be a mapping");
|
||||
});
|
||||
|
||||
it("throws on invalid models entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, models: { m: { provider: 123, name: "x" } } }))
|
||||
.toThrow("config.models.m requires provider and name");
|
||||
});
|
||||
|
||||
it("throws on invalid agents entry", () => {
|
||||
expect(() => normalizeWorkflowConfig({ ...VALID_CONFIG, agents: "bad" }))
|
||||
.toThrow("config.agents must be a mapping");
|
||||
});
|
||||
|
||||
it("returns null for undefined modelOverrides", () => {
|
||||
const result = normalizeWorkflowConfig(VALID_CONFIG);
|
||||
expect(result.modelOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for null agentOverrides", () => {
|
||||
const result = normalizeWorkflowConfig({ ...VALID_CONFIG, agentOverrides: null });
|
||||
expect(result.agentOverrides).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes agentOverrides with nested roles", () => {
|
||||
const config = {
|
||||
...VALID_CONFIG,
|
||||
agentOverrides: {
|
||||
"solve-issue": { coder: "hermes", reviewer: "claude" },
|
||||
},
|
||||
};
|
||||
const result = normalizeWorkflowConfig(config);
|
||||
expect(result.agentOverrides).toEqual({
|
||||
"solve-issue": { coder: "hermes", reviewer: "claude" },
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes modelOverrides", () => {
|
||||
const config = { ...VALID_CONFIG, modelOverrides: { coding: "fast" } };
|
||||
const result = normalizeWorkflowConfig(config);
|
||||
expect(result.modelOverrides).toEqual({ coding: "fast" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user