diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.test.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.test.tsx new file mode 100644 index 00000000000..0cc95033dde --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.test.tsx @@ -0,0 +1,61 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import PassThroughGuardrailsSection from "./PassThroughGuardrailsSection"; + +vi.mock("../networking", async (importOriginal) => ({ + ...(await importOriginal()), + getGuardrailsList: vi.fn(async () => ({ guardrails: [{ guardrail_name: "pii-guard" }] })), +})); + +const FIELDS = [ + { label: "Request Fields", matcher: /Request Fields/, payloadKey: "request_fields", typed: "query" }, + { label: "Response Fields", matcher: /Response Fields/, payloadKey: "response_fields", typed: "choices.content" }, +] as const; + +const renderSection = (disabled: boolean) => { + const onChange = vi.fn(); + render( + , + ); + return onChange; +}; + +const fieldInput = (matcher: RegExp) => screen.getByLabelText(matcher) as HTMLInputElement; + +describe("PassThroughGuardrailsSection field targeting", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it.each(FIELDS)( + "commits a typed $label entry while the section is enabled", + async ({ matcher, payloadKey, typed }) => { + const user = userEvent.setup(); + const onChange = renderSection(false); + + await user.type(fieldInput(matcher), `${typed},`); + + expect(onChange.mock.calls.at(-1)?.[0]).toStrictEqual({ "pii-guard": { [payloadKey]: [typed] } }); + }, + ); + + it.each(FIELDS)("refuses typed $label input while the section is disabled", async ({ matcher, typed }) => { + const user = userEvent.setup(); + const onChange = renderSection(true); + const input = fieldInput(matcher); + + await user.type(input, `${typed},`); + await user.type(input, "sneaked-in{Enter}"); + await user.tab(); + + expect(input.value).toBe(""); + expect(onChange).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx index 2c6856cb8b8..f51629b7ee1 100644 --- a/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PassThroughGuardrailsSection.tsx @@ -174,6 +174,7 @@ const PassThroughGuardrailsSection: React.FC value={value[guardrailName]?.request_fields ?? []} onValueChange={(fields) => handleFieldChange(guardrailName, "request_fields", fields)} tokenSeparators={[","]} + disabled={disabled} /> @@ -212,6 +213,7 @@ const PassThroughGuardrailsSection: React.FC value={value[guardrailName]?.response_fields ?? []} onValueChange={(fields) => handleFieldChange(guardrailName, "response_fields", fields)} tokenSeparators={[","]} + disabled={disabled} />