mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 17:11:42 -06:00
83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
/** Verifies hook decision type guards and normalization helpers. */
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
BLOCK_MESSAGE_PREFIX,
|
|
type HookDecision,
|
|
type HookDecisionBlock,
|
|
mergeHookDecisions,
|
|
isHookDecision,
|
|
resolveBlockMessage,
|
|
} from "./hook-decision-types.js";
|
|
|
|
describe("HookDecision helpers", () => {
|
|
describe("isHookDecision", () => {
|
|
it("recognizes supported outcomes", () => {
|
|
expect(isHookDecision({ outcome: "pass" })).toBe(true);
|
|
expect(isHookDecision({ outcome: "block", reason: "policy" })).toBe(true);
|
|
});
|
|
|
|
it("rejects non-decision values", () => {
|
|
expect(isHookDecision(null)).toBe(false);
|
|
expect(isHookDecision(undefined)).toBe(false);
|
|
expect(isHookDecision("pass")).toBe(false);
|
|
expect(isHookDecision({ block: true })).toBe(false);
|
|
expect(isHookDecision({ outcome: "ask", reason: "check" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "invalid" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "pass", message: "typo" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "pass", reason: "typo" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block", reason: "" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block", reason: "policy", message: "" })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block", reason: "policy", message: 3 })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block", reason: "policy", ask: true })).toBe(false);
|
|
expect(isHookDecision({ outcome: "block", reason: "policy", metadata: [] })).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("mergeHookDecisions", () => {
|
|
const passDecision: HookDecision = { outcome: "pass" };
|
|
const blockDecision: HookDecision = { outcome: "block", reason: "policy" };
|
|
|
|
it("uses most-restrictive-wins ordering", () => {
|
|
expect(mergeHookDecisions(undefined, passDecision)).toBe(passDecision);
|
|
expect(mergeHookDecisions(passDecision, blockDecision)).toBe(blockDecision);
|
|
expect(mergeHookDecisions(blockDecision, passDecision)).toBe(blockDecision);
|
|
});
|
|
|
|
it("keeps the first decision when outcomes have the same severity", () => {
|
|
const secondBlock: HookDecision = { outcome: "block", reason: "second" };
|
|
|
|
expect(mergeHookDecisions(passDecision, { outcome: "pass" })).toBe(passDecision);
|
|
expect(mergeHookDecisions(blockDecision, secondBlock)).toBe(blockDecision);
|
|
});
|
|
});
|
|
|
|
describe("resolveBlockMessage", () => {
|
|
it("returns explicit or default block messages", () => {
|
|
const explicit: HookDecisionBlock = {
|
|
outcome: "block",
|
|
reason: "policy",
|
|
message: "Please rephrase your request.",
|
|
};
|
|
const fallback: HookDecisionBlock = {
|
|
outcome: "block",
|
|
reason: "policy",
|
|
};
|
|
|
|
expect(resolveBlockMessage(explicit)).toBe(
|
|
`${BLOCK_MESSAGE_PREFIX}: Please rephrase your request.`,
|
|
);
|
|
expect(resolveBlockMessage(fallback)).toBe(`${BLOCK_MESSAGE_PREFIX}: blocked`);
|
|
expect(resolveBlockMessage(fallback, { blockedBy: "policy-plugin" })).toBe(
|
|
`${BLOCK_MESSAGE_PREFIX}: blocked by policy-plugin`,
|
|
);
|
|
expect(resolveBlockMessage(explicit, { blockedBy: "policy-plugin" })).toBe(
|
|
`${BLOCK_MESSAGE_PREFIX}: Please rephrase your request. (blocked by policy-plugin)`,
|
|
);
|
|
expect(resolveBlockMessage({ ...explicit, message: " " })).toBe(
|
|
`${BLOCK_MESSAGE_PREFIX}: blocked`,
|
|
);
|
|
});
|
|
});
|
|
});
|