test: tighten approve command assertions

This commit is contained in:
Peter Steinberger
2026-05-10 22:02:04 +01:00
parent 5da90d0f4f
commit 01eca8bc79

View File

@@ -25,6 +25,38 @@ vi.mock("../../globals.js", () => ({
logVerbose: vi.fn(),
}));
function requireRecord(value: unknown, label: string): Record<string, unknown> {
expect(value).toBeTypeOf("object");
expect(value).not.toBeNull();
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error(`expected ${label} to be an object`);
}
return value as Record<string, unknown>;
}
function gatewayRequest(callIndex = 0) {
const call = callGatewayMock.mock.calls[callIndex] as unknown[] | undefined;
expect(call).toBeDefined();
if (!call) {
throw new Error(`expected gateway call ${callIndex}`);
}
return requireRecord(call[0], `gateway call ${callIndex} request`);
}
function expectGatewayResolveCall(params: {
callIndex?: number;
method: string;
id: string;
decision?: string;
}) {
const request = gatewayRequest(params.callIndex ?? 0);
expect(request.method).toBe(params.method);
expect(request.params).toEqual({
id: params.id,
decision: params.decision ?? "allow-once",
});
}
function normalizeDiscordDirectApproverId(value: string | number): string | undefined {
const normalized = String(value)
.trim()
@@ -468,12 +500,7 @@ describe("handleApproveCommand", () => {
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc" });
});
it("accepts bare approve text for Slack-style manual approvals", async () => {
@@ -496,12 +523,7 @@ describe("handleApproveCommand", () => {
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc" });
});
it("accepts Telegram /approve from configured approvers even when chat access is otherwise blocked", async () => {
@@ -516,12 +538,7 @@ describe("handleApproveCommand", () => {
const result = await handleApproveCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc12345", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc12345" });
});
it("honors the configured default account for omitted-account /approve auth", async () => {
@@ -563,12 +580,7 @@ describe("handleApproveCommand", () => {
const result = await handleApproveCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc12345", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc12345" });
});
it("accepts Signal /approve from configured approvers even when chat access is otherwise blocked", async () => {
@@ -594,12 +606,7 @@ describe("handleApproveCommand", () => {
const result = await handleApproveCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc12345", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc12345" });
});
it("does not treat implicit default approval auth as a bypass for unauthorized senders", async () => {
@@ -706,12 +713,7 @@ describe("handleApproveCommand", () => {
const result = await handleApproveCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc12345", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc12345" });
});
it("accepts Telegram /approve from exec target recipients when native approvals are disabled", async () => {
@@ -744,12 +746,7 @@ describe("handleApproveCommand", () => {
const result = await handleApproveCommand(params, true);
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc12345", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "exec.approval.resolve", id: "abc12345" });
});
it("requires configured Discord approvers for exec approvals", async () => {
@@ -800,13 +797,11 @@ describe("handleApproveCommand", () => {
expect(result?.shouldContinue, testCase.name).toBe(false);
expect(result?.reply?.text, testCase.name).toContain(testCase.expectedText);
expect(callGatewayMock, testCase.name).toHaveBeenCalledTimes(testCase.expectedGatewayCalls);
if ("expectedMethod" in testCase) {
expect(callGatewayMock, testCase.name).toHaveBeenCalledWith(
expect.objectContaining({
method: testCase.expectedMethod,
params: { id: "abc12345", decision: "allow-once" },
}),
);
if ("expectedMethod" in testCase && testCase.expectedMethod) {
expectGatewayResolveCall({
method: testCase.expectedMethod,
id: "abc12345",
});
}
}
});
@@ -859,13 +854,11 @@ describe("handleApproveCommand", () => {
expect(result?.shouldContinue).toBe(false);
expect(result?.reply?.text).toContain("Approval allow-once submitted");
expect(callGatewayMock).toHaveBeenCalledTimes(2);
expect(callGatewayMock).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
method: "plugin.approval.resolve",
params: { id: "legacy-plugin-123", decision: "allow-once" },
}),
);
expectGatewayResolveCall({
callIndex: 1,
method: "plugin.approval.resolve",
id: "legacy-plugin-123",
});
});
it("returns the underlying not-found error for plugin-only approval routing", async () => {
@@ -911,12 +904,7 @@ describe("handleApproveCommand", () => {
expect(result?.reply?.text).toContain("Failed to submit approval");
expect(result?.reply?.text).toContain("unknown or expired approval id");
expect(callGatewayMock).toHaveBeenCalledTimes(1);
expect(callGatewayMock).toHaveBeenCalledWith(
expect.objectContaining({
method: "plugin.approval.resolve",
params: { id: "abc123", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "plugin.approval.resolve", id: "abc123" });
});
it("requires configured Discord approvers for plugin approvals", async () => {
@@ -952,12 +940,7 @@ describe("handleApproveCommand", () => {
expect(result?.reply?.text, testCase.name).toContain(testCase.expectedText);
expect(callGatewayMock, testCase.name).toHaveBeenCalledTimes(testCase.expectedGatewayCalls);
if (testCase.expectedGatewayCalls > 0) {
expect(callGatewayMock, testCase.name).toHaveBeenCalledWith(
expect.objectContaining({
method: "plugin.approval.resolve",
params: { id: "plugin:abc123", decision: "allow-once" },
}),
);
expectGatewayResolveCall({ method: "plugin.approval.resolve", id: "plugin:abc123" });
}
}
});
@@ -1064,12 +1047,11 @@ describe("handleApproveCommand", () => {
testCase.expectedGatewayCalls,
);
if (testCase.expectedGatewayCalls > 0) {
expect(callGatewayMock, String(testCase.scopes)).toHaveBeenLastCalledWith(
expect.objectContaining({
method: "exec.approval.resolve",
params: { id: "abc", decision: "allow-once" },
}),
);
expectGatewayResolveCall({
callIndex: callGatewayMock.mock.calls.length - 1,
method: "exec.approval.resolve",
id: "abc",
});
}
}
});