Files
homarr/apps/nextjs/src/components/assistant/assistant-auto-approval.spec.ts
T

127 lines
4.2 KiB
TypeScript

// @vitest-environment jsdom
import { act, createElement } from "react";
import { createRoot } from "react-dom/client";
import { afterEach, describe, expect, test } from "vitest";
import {
AssistantAutoApprovalProvider,
createAssistantAutoApprovalTracker,
useAssistantAutoApproval,
} from "./assistant-auto-approval";
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
type AutoApproval = ReturnType<typeof useAssistantAutoApproval>;
let currentAutoApproval: AutoApproval | null = null;
const containers: HTMLDivElement[] = [];
const AutoApprovalProbe = () => {
currentAutoApproval = useAssistantAutoApproval();
return null;
};
afterEach(() => {
currentAutoApproval = null;
for (const container of containers.splice(0)) container.remove();
});
describe("assistant automatic approval tracker", () => {
test("claims a tool call only once across duplicate conversation renderers", () => {
const tracker = createAssistantAutoApprovalTracker();
expect(tracker.claim("app-create-1")).toBe(true);
expect(tracker.claim("app-create-1")).toBe(false);
expect(tracker.claim("board-save-1")).toBe(true);
});
test("can retry a failed response and clears claims for another conversation", () => {
const tracker = createAssistantAutoApprovalTracker();
expect(tracker.claim("app-create-1")).toBe(true);
tracker.release("app-create-1");
expect(tracker.claim("app-create-1")).toBe(true);
tracker.clear();
expect(tracker.claim("app-create-1")).toBe(true);
});
test("keeps the setting for the same local conversation and resets it on a real switch", async () => {
const container = document.createElement("div");
containers.push(container);
document.body.append(container);
const root = createRoot(container);
const renderConversation = async (conversationId: string) => {
await act(async () => {
root.render(createElement(AssistantAutoApprovalProvider, { conversationId }, createElement(AutoApprovalProbe)));
});
};
await renderConversation("local-thread-1");
act(() => currentAutoApproval?.setEnabled(true));
expect(currentAutoApproval?.enabled).toBe(true);
await renderConversation("local-thread-1");
expect(currentAutoApproval?.enabled).toBe(true);
await renderConversation("local-thread-2");
expect(currentAutoApproval?.enabled).toBe(false);
await act(async () => root.unmount());
});
test("clears claimed calls when automatic approvals are turned off", async () => {
const container = document.createElement("div");
containers.push(container);
document.body.append(container);
const root = createRoot(container);
await act(async () => {
root.render(
createElement(
AssistantAutoApprovalProvider,
{ conversationId: "local-thread-1" },
createElement(AutoApprovalProbe),
),
);
});
act(() => currentAutoApproval?.setEnabled(true));
expect(currentAutoApproval?.requestApproval("app-create-1", () => undefined)).toBe(true);
expect(currentAutoApproval?.requestApproval("app-create-1", () => undefined)).toBe(false);
act(() => currentAutoApproval?.setEnabled(false));
act(() => currentAutoApproval?.setEnabled(true));
expect(currentAutoApproval?.requestApproval("app-create-1", () => undefined)).toBe(true);
await act(async () => root.unmount());
});
test("returns a failed approval to the caller and releases its claim", async () => {
const container = document.createElement("div");
containers.push(container);
document.body.append(container);
const root = createRoot(container);
await act(async () => {
root.render(
createElement(
AssistantAutoApprovalProvider,
{ conversationId: "local-thread-1" },
createElement(AutoApprovalProbe),
),
);
});
act(() => currentAutoApproval?.setEnabled(true));
expect(
currentAutoApproval?.requestApproval("app-create-1", () => {
throw new Error("Approval transport failed");
}),
).toBe(false);
expect(currentAutoApproval?.requestApproval("app-create-1", () => undefined)).toBe(true);
await act(async () => root.unmount());
});
});