test: clear crestodian operations broad matchers

This commit is contained in:
Peter Steinberger
2026-05-10 12:55:15 +01:00
parent e384932497
commit 9a46159b3f
+165 -119
View File
@@ -4,11 +4,7 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime.js";
import { createCrestodianTestRuntime } from "./crestodian.test-helpers.js";
import {
executeCrestodianOperation,
parseCrestodianOperation,
type CrestodianOperationResult,
} from "./operations.js";
import { executeCrestodianOperation, parseCrestodianOperation } from "./operations.js";
type TestConfig = Record<string, unknown>;
@@ -20,6 +16,45 @@ function parseLastJsonLine(raw: string): unknown {
return JSON.parse(lastLine) as unknown;
}
function requireRecord(value: unknown, label: string): Record<string, unknown> {
expect(typeof value).toBe("object");
expect(value).not.toBeNull();
if (typeof value !== "object" || value === null) {
throw new Error(`${label} was not an object`);
}
return value as Record<string, unknown>;
}
function expectRecordFields(record: Record<string, unknown>, fields: Record<string, unknown>) {
for (const [key, value] of Object.entries(fields)) {
expect(record[key]).toEqual(value);
}
}
function expectAuditRecord(
audit: unknown,
fields: Record<string, unknown>,
detailFields: Record<string, unknown>,
) {
const auditRecord = requireRecord(audit, "audit record");
expectRecordFields(auditRecord, fields);
expectRecordFields(requireRecord(auditRecord.details, "audit details"), detailFields);
}
function requireFirstMockCall(mock: unknown, label: string): unknown[] {
const call = (mock as { mock?: { calls?: unknown[][] } }).mock?.calls?.[0];
expect(call).toBeDefined();
if (!call) {
throw new Error(`missing ${label} call`);
}
return call;
}
function expectRuntimeArg(value: unknown) {
const runtime = requireRecord(value, "runtime argument");
expect(typeof runtime.log).toBe("function");
}
const mockConfig = vi.hoisted(() => {
const initial = {};
const state = {
@@ -267,7 +302,7 @@ describe("parseCrestodianOperation", () => {
deps: { runGatewayRestart },
});
expect(result).toMatchObject<CrestodianOperationResult>({
expectRecordFields(result as unknown as Record<string, unknown>, {
applied: false,
message: "Plan: restart the Gateway. Say yes to apply.",
});
@@ -279,9 +314,8 @@ describe("parseCrestodianOperation", () => {
mockConfig.missing("/tmp/openclaw.json");
const { runtime, lines } = createCrestodianTestRuntime();
await expect(
executeCrestodianOperation({ kind: "config-validate" }, runtime),
).resolves.toMatchObject({ applied: false });
const result = await executeCrestodianOperation({ kind: "config-validate" }, runtime);
expect(result.applied).toBe(false);
expect(lines.join("\n")).toContain("Config missing:");
});
@@ -292,17 +326,16 @@ describe("parseCrestodianOperation", () => {
const { runtime, lines } = createCrestodianTestRuntime();
const runConfigSet = vi.fn(async () => {});
await expect(
executeCrestodianOperation(
{ kind: "config-set", path: "gateway.port", value: "19001" },
runtime,
{
approved: true,
deps: { runConfigSet },
auditDetails: { rescue: true, channel: "whatsapp" },
},
),
).resolves.toMatchObject({ applied: true });
const result = await executeCrestodianOperation(
{ kind: "config-set", path: "gateway.port", value: "19001" },
runtime,
{
approved: true,
deps: { runConfigSet },
auditDetails: { rescue: true, channel: "whatsapp" },
},
);
expect(result.applied).toBe(true);
expect(runConfigSet).toHaveBeenCalledWith({
path: "gateway.port",
@@ -312,15 +345,15 @@ describe("parseCrestodianOperation", () => {
expect(lines.join("\n")).toContain("[crestodian] done: config.set");
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = JSON.parse((await fs.readFile(auditPath, "utf8")).trim());
expect(audit).toMatchObject({
operation: "config.set",
summary: "Set config gateway.port",
details: {
expectAuditRecord(
audit,
{ operation: "config.set", summary: "Set config gateway.port" },
{
rescue: true,
channel: "whatsapp",
path: "gateway.port",
},
});
);
});
it("applies SecretRef config set through typed deps and writes an audit entry", async () => {
@@ -329,22 +362,21 @@ describe("parseCrestodianOperation", () => {
const { runtime, lines } = createCrestodianTestRuntime();
const runConfigSet = vi.fn(async () => {});
await expect(
executeCrestodianOperation(
{
kind: "config-set-ref",
path: "gateway.auth.token",
source: "env",
id: "OPENCLAW_GATEWAY_TOKEN",
},
runtime,
{
approved: true,
deps: { runConfigSet },
auditDetails: { rescue: true, channel: "whatsapp" },
},
),
).resolves.toMatchObject({ applied: true });
const result = await executeCrestodianOperation(
{
kind: "config-set-ref",
path: "gateway.auth.token",
source: "env",
id: "OPENCLAW_GATEWAY_TOKEN",
},
runtime,
{
approved: true,
deps: { runConfigSet },
auditDetails: { rescue: true, channel: "whatsapp" },
},
);
expect(result.applied).toBe(true);
expect(runConfigSet).toHaveBeenCalledWith({
path: "gateway.auth.token",
@@ -357,17 +389,20 @@ describe("parseCrestodianOperation", () => {
expect(lines.join("\n")).toContain("[crestodian] done: config.setRef");
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = JSON.parse((await fs.readFile(auditPath, "utf8")).trim());
expect(audit).toMatchObject({
operation: "config.setRef",
summary: "Set config gateway.auth.token SecretRef",
details: {
expectAuditRecord(
audit,
{
operation: "config.setRef",
summary: "Set config gateway.auth.token SecretRef",
},
{
rescue: true,
channel: "whatsapp",
path: "gateway.auth.token",
source: "env",
provider: "default",
},
});
);
});
it("runs plugin list and search as read-only operations", async () => {
@@ -379,16 +414,18 @@ describe("parseCrestodianOperation", () => {
pluginRuntime.log(`search rows: ${query}`);
});
await expect(
executeCrestodianOperation({ kind: "plugin-list" }, runtime, {
const listResult = await executeCrestodianOperation({ kind: "plugin-list" }, runtime, {
deps: { runPluginsList, runPluginsSearch },
});
expect(listResult.applied).toBe(false);
const searchResult = await executeCrestodianOperation(
{ kind: "plugin-search", query: "calendar" },
runtime,
{
deps: { runPluginsList, runPluginsSearch },
}),
).resolves.toMatchObject({ applied: false });
await expect(
executeCrestodianOperation({ kind: "plugin-search", query: "calendar" }, runtime, {
deps: { runPluginsList, runPluginsSearch },
}),
).resolves.toMatchObject({ applied: false });
},
);
expect(searchResult.applied).toBe(false);
expect(runPluginsList).toHaveBeenCalledWith(runtime);
expect(runPluginsSearch).toHaveBeenCalledWith("calendar", runtime);
@@ -410,36 +447,37 @@ describe("parseCrestodianOperation", () => {
runtime,
{ deps: { runPluginInstall } },
);
expect(plan).toMatchObject({
expectRecordFields(plan as unknown as Record<string, unknown>, {
applied: false,
message: "Plan: install plugin clawhub:openclaw-demo. Say yes to apply.",
});
expect(runPluginInstall).not.toHaveBeenCalled();
await expect(
executeCrestodianOperation(
{ kind: "plugin-install", spec: "clawhub:openclaw-demo" },
runtime,
{
approved: true,
deps: { runPluginInstall },
auditDetails: { rescue: true },
},
),
).resolves.toMatchObject({ applied: true });
const result = await executeCrestodianOperation(
{ kind: "plugin-install", spec: "clawhub:openclaw-demo" },
runtime,
{
approved: true,
deps: { runPluginInstall },
auditDetails: { rescue: true },
},
);
expect(result.applied).toBe(true);
expect(runPluginInstall).toHaveBeenCalledWith("clawhub:openclaw-demo", expect.any(Object));
const installCall = requireFirstMockCall(runPluginInstall, "runPluginInstall");
expect(installCall[0]).toBe("clawhub:openclaw-demo");
expectRuntimeArg(installCall[1]);
expect(lines.join("\n")).toContain("[crestodian] done: plugin.install");
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = JSON.parse((await fs.readFile(auditPath, "utf8")).trim());
expect(audit).toMatchObject({
operation: "plugin.install",
summary: "Installed plugin clawhub:openclaw-demo",
details: {
rescue: true,
spec: "clawhub:openclaw-demo",
expectAuditRecord(
audit,
{
operation: "plugin.install",
summary: "Installed plugin clawhub:openclaw-demo",
},
});
{ rescue: true, spec: "clawhub:openclaw-demo" },
);
});
it("uninstalls plugins only after approval and audits the write", async () => {
@@ -455,32 +493,37 @@ describe("parseCrestodianOperation", () => {
runtime,
{ deps: { runPluginUninstall } },
);
expect(plan).toMatchObject({
expectRecordFields(plan as unknown as Record<string, unknown>, {
applied: false,
message: "Plan: uninstall plugin openclaw-demo. Say yes to apply.",
});
expect(runPluginUninstall).not.toHaveBeenCalled();
await expect(
executeCrestodianOperation({ kind: "plugin-uninstall", pluginId: "openclaw-demo" }, runtime, {
const result = await executeCrestodianOperation(
{ kind: "plugin-uninstall", pluginId: "openclaw-demo" },
runtime,
{
approved: true,
deps: { runPluginUninstall },
auditDetails: { rescue: true },
}),
).resolves.toMatchObject({ applied: true });
},
);
expect(result.applied).toBe(true);
expect(runPluginUninstall).toHaveBeenCalledWith("openclaw-demo", expect.any(Object));
const uninstallCall = requireFirstMockCall(runPluginUninstall, "runPluginUninstall");
expect(uninstallCall[0]).toBe("openclaw-demo");
expectRuntimeArg(uninstallCall[1]);
expect(lines.join("\n")).toContain("[crestodian] done: plugin.uninstall");
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = JSON.parse((await fs.readFile(auditPath, "utf8")).trim());
expect(audit).toMatchObject({
operation: "plugin.uninstall",
summary: "Uninstalled plugin openclaw-demo",
details: {
rescue: true,
pluginId: "openclaw-demo",
expectAuditRecord(
audit,
{
operation: "plugin.uninstall",
summary: "Uninstalled plugin openclaw-demo",
},
});
{ rescue: true, pluginId: "openclaw-demo" },
);
});
it("runs setup bootstrap only after approval and audits it", async () => {
@@ -493,39 +536,43 @@ describe("parseCrestodianOperation", () => {
{ kind: "setup", workspace: "/tmp/work" },
runtime,
);
expect(plan).toMatchObject({
expectRecordFields(plan as unknown as Record<string, unknown>, {
applied: false,
});
expect(lines.join("\n")).toContain("Model choice: openai/gpt-5.5 (OPENAI_API_KEY).");
await expect(
executeCrestodianOperation({ kind: "setup", workspace: "/tmp/work" }, runtime, {
const result = await executeCrestodianOperation(
{ kind: "setup", workspace: "/tmp/work" },
runtime,
{
approved: true,
auditDetails: { rescue: true },
}),
).resolves.toMatchObject({ applied: true });
},
);
expect(result.applied).toBe(true);
expect(lines.join("\n")).toContain("[crestodian] done: crestodian.setup");
expect(mockConfig.currentConfig()).toMatchObject({
agents: {
defaults: {
workspace: "/tmp/work",
model: { primary: "openai/gpt-5.5" },
},
},
const config = requireRecord(mockConfig.currentConfig(), "current config");
const agents = requireRecord(config.agents, "agents config");
expectRecordFields(requireRecord(agents.defaults, "agent defaults"), {
workspace: "/tmp/work",
model: { primary: "openai/gpt-5.5" },
});
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = JSON.parse((await fs.readFile(auditPath, "utf8")).trim());
expect(audit).toMatchObject({
operation: "crestodian.setup",
summary: "Bootstrapped setup with openai/gpt-5.5",
details: {
expectAuditRecord(
audit,
{
operation: "crestodian.setup",
summary: "Bootstrapped setup with openai/gpt-5.5",
},
{
rescue: true,
workspace: "/tmp/work",
model: "openai/gpt-5.5",
modelSource: "OPENAI_API_KEY",
},
});
);
});
it("runs doctor repairs only after approval and audits them", async () => {
@@ -537,19 +584,18 @@ describe("parseCrestodianOperation", () => {
const plan = await executeCrestodianOperation({ kind: "doctor-fix" }, runtime, {
deps: { runDoctor },
});
expect(plan).toMatchObject({
expectRecordFields(plan as unknown as Record<string, unknown>, {
applied: false,
message: "Plan: run doctor repairs. Say yes to apply.",
});
expect(runDoctor).not.toHaveBeenCalled();
await expect(
executeCrestodianOperation({ kind: "doctor-fix" }, runtime, {
approved: true,
deps: { runDoctor },
auditDetails: { rescue: true },
}),
).resolves.toMatchObject({ applied: true });
const result = await executeCrestodianOperation({ kind: "doctor-fix" }, runtime, {
approved: true,
deps: { runDoctor },
auditDetails: { rescue: true },
});
expect(result.applied).toBe(true);
expect(runDoctor).toHaveBeenCalledWith(runtime, {
nonInteractive: true,
@@ -559,11 +605,11 @@ describe("parseCrestodianOperation", () => {
expect(lines.join("\n")).toContain("[crestodian] done: doctor.fix");
const auditPath = path.join(tempDir, "audit", "crestodian.jsonl");
const audit = parseLastJsonLine(await fs.readFile(auditPath, "utf8"));
expect(audit).toMatchObject({
operation: "doctor.fix",
summary: "Ran doctor repairs",
details: { rescue: true },
});
expectAuditRecord(
audit,
{ operation: "doctor.fix", summary: "Ran doctor repairs" },
{ rescue: true },
);
});
it("returns from the agent TUI back to Crestodian", async () => {
@@ -587,7 +633,7 @@ describe("parseCrestodianOperation", () => {
deliver: false,
historyLimit: 200,
});
expect(result).toMatchObject({
expectRecordFields(result as unknown as Record<string, unknown>, {
applied: false,
nextInput: "restart gateway",
});