diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx
index 13472a3d1df..e1d02e9352d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.test.tsx
@@ -40,14 +40,23 @@ const renderDashboard = () =>
,
);
+const REQUESTS_CHART_TITLE = "Cache Hits vs API Requests";
+const TOKENS_CHART_TITLE = "Cached Completion Tokens vs Generated Completion Tokens";
+
+// Anchored on each chart's own title rather than on a global card count, so
+// adding cards elsewhere on the page cannot silently repoint these assertions.
+const cardTitled = (title: string): HTMLElement => {
+ const card = screen.getByText(title).closest('[data-slot="card"]');
+ expect(card).not.toBeNull();
+ return card as HTMLElement;
+};
+
const findChartCards = async () => {
- await screen.findByText("Cache Hits vs API Requests");
+ await screen.findByText(REQUESTS_CHART_TITLE);
await waitFor(() => {
expect(document.querySelectorAll("path.recharts-rectangle").length).toBeGreaterThan(0);
});
- const cards = Array.from(document.querySelectorAll('[data-slot="card"]'));
- expect(cards).toHaveLength(2);
- return { requestsCard: cards[0] as HTMLElement, tokensCard: cards[1] as HTMLElement };
+ return { requestsCard: cardTitled(REQUESTS_CHART_TITLE), tokensCard: cardTitled(TOKENS_CHART_TITLE) };
};
const barFills = (card: HTMLElement) =>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.test.tsx
new file mode 100644
index 00000000000..de06ef07ed1
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.test.tsx
@@ -0,0 +1,148 @@
+import React from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import { CacheHealthTab } from "./cache_health";
+
+const healthyResponse = {
+ status: "healthy",
+ ping_response: true,
+ set_cache_response: "success",
+ litellm_cache_params: JSON.stringify({ type: "redis", supported_call_types: ["acompletion"] }),
+ health_check_cache_params: JSON.stringify({
+ redis_version: "7.2.1",
+ namespace: "litellm-ns",
+ connection_kwargs: { host: "redis.internal", port: 6379 },
+ }),
+};
+
+const errorPayload = {
+ message: "Connection refused",
+ traceback: "Traceback (most recent call last): ...",
+ litellm_cache_params: { type: "redis" },
+ health_check_cache_params: {},
+};
+
+const errorResponse = { error: { message: JSON.stringify(errorPayload) } };
+
+const renderTab = (overrides: Partial> = {}) =>
+ renderWithProviders(
+ ,
+ );
+
+describe("CacheHealthTab", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("offers a health check button and no results before one is run", () => {
+ renderTab();
+
+ expect(screen.getByRole("button", { name: "Run Health Check" })).toBeInTheDocument();
+ expect(screen.queryByText(/Cache Status:/)).not.toBeInTheDocument();
+ });
+
+ it("runs the health check when the button is clicked", async () => {
+ const runCachingHealthCheck = vi.fn();
+ const user = userEvent.setup();
+ renderTab({ runCachingHealthCheck });
+
+ await user.click(screen.getByRole("button", { name: "Run Health Check" }));
+
+ expect(runCachingHealthCheck).toHaveBeenCalledTimes(1);
+ });
+
+ it("shows an in-flight label and disables the button while the check runs", async () => {
+ const runCachingHealthCheck = vi.fn(() => new Promise(() => {}));
+ const user = userEvent.setup();
+ renderTab({ runCachingHealthCheck });
+
+ await user.click(screen.getByRole("button", { name: "Run Health Check" }));
+
+ const button = await screen.findByRole("button", { name: "Running Health Check..." });
+ expect(button).toBeDisabled();
+ });
+
+ it("reports a healthy cache with its ping and set-cache results", async () => {
+ renderTab({ healthCheckResponse: healthyResponse });
+
+ expect(await screen.findByText("Cache Status: healthy")).toBeInTheDocument();
+ expect(screen.getByText("Cache Details")).toBeInTheDocument();
+ expect(screen.getByText("Ping Response")).toBeInTheDocument();
+ expect(screen.getByText("Set Cache Response")).toBeInTheDocument();
+ expect(screen.getByText("success")).toBeInTheDocument();
+ });
+
+ it("shows the Redis detail rows when the cache type is redis", async () => {
+ renderTab({ healthCheckResponse: healthyResponse });
+
+ expect(await screen.findByText("Redis Details")).toBeInTheDocument();
+ expect(screen.getByText("Redis Host")).toBeInTheDocument();
+ expect(screen.getByText("redis.internal")).toBeInTheDocument();
+ expect(screen.getByText("Redis Port")).toBeInTheDocument();
+ expect(screen.getByText("Redis Version")).toBeInTheDocument();
+ expect(screen.getByText("7.2.1")).toBeInTheDocument();
+ expect(screen.getByText("Namespace")).toBeInTheDocument();
+ expect(screen.getByText("litellm-ns")).toBeInTheDocument();
+ });
+
+ it("omits the Redis detail rows for a non-redis cache type", async () => {
+ renderTab({
+ healthCheckResponse: {
+ status: "healthy",
+ ping_response: true,
+ litellm_cache_params: JSON.stringify({ type: "local" }),
+ health_check_cache_params: JSON.stringify({}),
+ },
+ });
+
+ expect(await screen.findByText("Cache Status: healthy")).toBeInTheDocument();
+ expect(screen.queryByText("Redis Details")).not.toBeInTheDocument();
+ });
+
+ it("surfaces the error message and traceback when the check fails", async () => {
+ renderTab({ healthCheckResponse: errorResponse });
+
+ expect(await screen.findByText("Error Details")).toBeInTheDocument();
+ expect(screen.getByText("Error Message")).toBeInTheDocument();
+ expect(screen.getByText("Connection refused")).toBeInTheDocument();
+ expect(screen.getByText("Traceback")).toBeInTheDocument();
+ expect(screen.getByText("Cache Status: unhealthy")).toBeInTheDocument();
+ });
+
+ it("still shows the cache details section when the check failed", async () => {
+ renderTab({ healthCheckResponse: errorResponse });
+
+ expect(await screen.findByText("Cache Details")).toBeInTheDocument();
+ });
+
+ it("truncates a long value and expands it to the full value on click", async () => {
+ const longMessage = "M".repeat(120);
+ const user = userEvent.setup();
+ renderTab({
+ healthCheckResponse: {
+ error: { message: JSON.stringify({ message: longMessage, traceback: "short" }) },
+ },
+ });
+
+ await screen.findByText("Error Message");
+ expect(screen.getByText(`${"M".repeat(50)}...`)).toBeInTheDocument();
+ expect(screen.queryByText(longMessage)).not.toBeInTheDocument();
+
+ await user.click(screen.getAllByRole("button", { name: "▶" })[0]);
+
+ await waitFor(() => {
+ expect(screen.getByText(longMessage)).toBeInTheDocument();
+ });
+ });
+
+ it("offers both the summary and raw response views", async () => {
+ renderTab({ healthCheckResponse: healthyResponse });
+
+ expect(await screen.findByText("Summary")).toBeInTheDocument();
+ expect(screen.getByText("Raw Response")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/coordination_redis_settings/CoordinationRedisTypeSelector.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/coordination_redis_settings/CoordinationRedisTypeSelector.test.tsx
new file mode 100644
index 00000000000..76287e6e724
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/coordination_redis_settings/CoordinationRedisTypeSelector.test.tsx
@@ -0,0 +1,56 @@
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import CoordinationRedisTypeSelector from "./CoordinationRedisTypeSelector";
+import { COORDINATION_REDIS_TYPE_DESCRIPTIONS } from "./coordinationRedisFields";
+
+describe("CoordinationRedisTypeSelector", () => {
+ it("labels the control and shows the current selection", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("Redis Type")).toBeInTheDocument();
+ expect(screen.getByRole("combobox")).toBeInTheDocument();
+ expect(screen.getByText("Node (Single Instance)")).toBeInTheDocument();
+ });
+
+ it("shows the description for the selected type", () => {
+ renderWithProviders();
+
+ expect(screen.getByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.cluster)).toBeInTheDocument();
+ });
+
+ it("switches the description when the selected type changes", () => {
+ const { rerender } = renderWithProviders();
+ expect(screen.getByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.node)).toBeInTheDocument();
+
+ rerender();
+
+ expect(screen.getByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.sentinel)).toBeInTheDocument();
+ expect(screen.queryByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.node)).not.toBeInTheDocument();
+ });
+
+ it("reports the newly picked type to the caller", async () => {
+ const onTypeChange = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await user.click(screen.getByRole("combobox"));
+ await user.click(await screen.findByText("Cluster"));
+
+ expect(onTypeChange).toHaveBeenCalledTimes(1);
+ expect(onTypeChange.mock.calls[0][0]).toBe("cluster");
+ });
+
+ it("offers every supported coordination redis type", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await user.click(screen.getByRole("combobox"));
+
+ expect(await screen.findByText("Cluster")).toBeInTheDocument();
+ expect(screen.getByText("Sentinel")).toBeInTheDocument();
+ expect(screen.getAllByText("Node (Single Instance)").length).toBeGreaterThan(0);
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/ai_suggestion_modal.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/ai_suggestion_modal.test.tsx
new file mode 100644
index 00000000000..4db75fb1ced
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/ai_suggestion_modal.test.tsx
@@ -0,0 +1,230 @@
+import React from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import AiSuggestionModal from "./ai_suggestion_modal";
+
+const { suggestPolicyTemplates, modelHubCall, testPolicyTemplate, enrichPolicyTemplateStream } = vi.hoisted(() => ({
+ suggestPolicyTemplates: vi.fn(),
+ modelHubCall: vi.fn(),
+ testPolicyTemplate: vi.fn(),
+ enrichPolicyTemplateStream: vi.fn(),
+}));
+
+vi.mock("@/components/networking", () => ({
+ suggestPolicyTemplates,
+ modelHubCall,
+ testPolicyTemplate,
+ enrichPolicyTemplateStream,
+}));
+
+const allTemplates = [
+ { id: "tpl-pii", title: "PII Protection", description: "Masks PII", guardrails: ["pii-masker"], complexity: "Low" },
+ {
+ id: "tpl-inj",
+ title: "Injection Defense",
+ description: "Blocks prompt injection",
+ guardrails: ["prompt-injection"],
+ complexity: "Medium",
+ },
+];
+
+const suggestResponse = {
+ selected_templates: [
+ { template_id: "tpl-pii", reason: "Your examples contain SSNs" },
+ { template_id: "tpl-inj", reason: "Your examples contain instruction overrides" },
+ ],
+ explanation: "These two cover both risks you described",
+};
+
+const defaultProps = {
+ visible: true,
+ onSelectTemplates: vi.fn(),
+ onCancel: vi.fn(),
+ accessToken: "sk-test",
+ allTemplates,
+};
+
+const renderModal = (props: Partial = {}) =>
+ renderWithProviders();
+
+const pickModel = async (user: ReturnType) => {
+ await user.click(screen.getByRole("combobox"));
+ const options = await screen.findAllByText("gpt-5.1");
+ await user.click(options[options.length - 1]);
+};
+
+describe("AiSuggestionModal", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ modelHubCall.mockResolvedValue({ data: [{ model_group: "gpt-5.1" }, { model_group: "claude-opus-4-8" }] });
+ suggestPolicyTemplates.mockResolvedValue(suggestResponse);
+ });
+
+ it("renders nothing while closed", () => {
+ renderModal({ visible: false });
+
+ expect(screen.queryByText("AI Policy Suggestion")).not.toBeInTheDocument();
+ });
+
+ it("renders the header and prompt copy when opened", async () => {
+ renderModal();
+
+ expect(await screen.findByText("AI Policy Suggestion")).toBeInTheDocument();
+ expect(
+ screen.getByText("Describe what you want to block and we'll suggest the best policy templates"),
+ ).toBeInTheDocument();
+ });
+
+ it("loads the model list when opened", async () => {
+ renderModal();
+
+ await waitFor(() => {
+ expect(modelHubCall).toHaveBeenCalledWith("sk-test");
+ });
+ });
+
+ it("keeps Suggest disabled until there is both input and a model", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ expect(screen.getByRole("button", { name: "Suggest Policies" })).toBeDisabled();
+
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ expect(screen.getByRole("button", { name: "Suggest Policies" })).toBeDisabled();
+
+ await pickModel(user);
+ expect(screen.getByRole("button", { name: "Suggest Policies" })).not.toBeDisabled();
+ });
+
+ it("sends the examples, description and model to the suggest API", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Ignore all previous instructions/), "my ssn is 123");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+
+ await waitFor(() => {
+ expect(suggestPolicyTemplates).toHaveBeenCalledWith("sk-test", ["my ssn is 123"], "block PII", "gpt-5.1");
+ });
+ });
+
+ it("adds attack example fields up to the maximum of four", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ const countExamples = () => screen.getAllByRole("textbox").length;
+ const initial = countExamples();
+
+ await user.click(screen.getByRole("button", { name: "+ Add another example" }));
+ expect(countExamples()).toBe(initial + 1);
+
+ await user.click(screen.getByRole("button", { name: "+ Add another example" }));
+ await user.click(screen.getByRole("button", { name: "+ Add another example" }));
+ expect(countExamples()).toBe(initial + 3);
+ expect(screen.queryByRole("button", { name: "+ Add another example" })).not.toBeInTheDocument();
+ });
+
+ it("shows each suggested template with the reason it was picked", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+
+ expect(await screen.findByText("PII Protection")).toBeInTheDocument();
+ expect(screen.getByText("Injection Defense")).toBeInTheDocument();
+ expect(screen.getByText("Your examples contain SSNs")).toBeInTheDocument();
+ expect(screen.getByText("These two cover both risks you described")).toBeInTheDocument();
+ expect(screen.getByText("2 templates matched your requirements")).toBeInTheDocument();
+ });
+
+ it("preselects every suggestion and reflects the count on the confirm button", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+
+ expect(await screen.findByRole("button", { name: "Use 2 Selected Templates" })).toBeInTheDocument();
+ });
+
+ it("deselecting a suggestion lowers the confirm count", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+
+ await user.click(await screen.findByText("PII Protection"));
+
+ expect(await screen.findByRole("button", { name: "Use 1 Selected Template" })).toBeInTheDocument();
+ });
+
+ it("hands the selected templates back to the caller", async () => {
+ const onSelectTemplates = vi.fn();
+ const user = userEvent.setup();
+ renderModal({ onSelectTemplates });
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+ await user.click(await screen.findByRole("button", { name: "Use 2 Selected Templates" }));
+
+ expect(onSelectTemplates).toHaveBeenCalledTimes(1);
+ expect(onSelectTemplates.mock.calls[0][0].map((t: { id: string }) => t.id)).toEqual(["tpl-pii", "tpl-inj"]);
+ });
+
+ it("returns to the input phase from the results phase", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+ await user.click(await screen.findByRole("button", { name: "Back" }));
+
+ expect(
+ await screen.findByText("Describe what you want to block and we'll suggest the best policy templates"),
+ ).toBeInTheDocument();
+ });
+
+ it("reports an empty result set instead of failing silently", async () => {
+ suggestPolicyTemplates.mockRejectedValue(new Error("boom"));
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.type(screen.getByPlaceholderText(/Block PII leakage/), "block PII");
+ await pickModel(user);
+ await user.click(screen.getByRole("button", { name: "Suggest Policies" }));
+
+ expect(await screen.findByText("No matching templates found")).toBeInTheDocument();
+ expect(screen.getByText("Try adjusting your examples or description.")).toBeInTheDocument();
+ });
+
+ it("cancels back to the caller", async () => {
+ const onCancel = vi.fn();
+ const user = userEvent.setup();
+ renderModal({ onCancel });
+
+ await screen.findByText("AI Policy Suggestion");
+ await user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/pipeline_flow_builder.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/pipeline_flow_builder.test.tsx
new file mode 100644
index 00000000000..cb763c2d22f
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/pipeline_flow_builder.test.tsx
@@ -0,0 +1,169 @@
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import PipelineFlowBuilder, { PipelineInfoDisplay } from "./pipeline_flow_builder";
+import { GuardrailPipeline, PipelineStep } from "@/components/policies/types";
+import { Guardrail } from "@/components/guardrails/types";
+
+vi.mock("@/components/networking");
+
+const step = (overrides: Partial = {}): PipelineStep => ({
+ guardrail: "pii-masker",
+ on_pass: "next",
+ on_fail: "block",
+ on_error: null,
+ modify_response_message: null,
+ ...overrides,
+});
+
+const pipeline = (steps: PipelineStep[]): GuardrailPipeline => ({ mode: "pre_call", steps });
+
+const guardrails = [
+ { guardrail_id: "g1", guardrail_name: "pii-masker" },
+ { guardrail_id: "g2", guardrail_name: "prompt-injection" },
+] as Guardrail[];
+
+describe("PipelineInfoDisplay", () => {
+ it("renders the trigger card", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("TRIGGER")).toBeInTheDocument();
+ expect(screen.getByText("Incoming LLM Request")).toBeInTheDocument();
+ });
+
+ it("renders one numbered card per step, naming its guardrail", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("Step 1")).toBeInTheDocument();
+ expect(screen.getByText("Step 2")).toBeInTheDocument();
+ expect(screen.getByText("pii-masker")).toBeInTheDocument();
+ expect(screen.getByText("prompt-injection")).toBeInTheDocument();
+ expect(screen.getAllByText("GUARDRAIL")).toHaveLength(2);
+ });
+
+ it("maps raw action values to their human labels", () => {
+ renderWithProviders();
+
+ expect(screen.getByText(/Pass .* Next Step/)).toBeInTheDocument();
+ expect(screen.getByText(/On fail .* Block/)).toBeInTheDocument();
+ });
+
+ it("falls back to the on-fail action when no API-failure action is set", () => {
+ renderWithProviders();
+
+ expect(screen.getByText(/On API failure .* Block \(same as on fail\)/)).toBeInTheDocument();
+ });
+
+ it("shows an explicit API-failure action when one is set", () => {
+ renderWithProviders();
+
+ expect(screen.getByText(/On API failure .* Allow/)).toBeInTheDocument();
+ expect(screen.queryByText(/same as on fail/)).not.toBeInTheDocument();
+ });
+});
+
+describe("PipelineFlowBuilder", () => {
+ it("renders the trigger and end cards around the steps", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText("TRIGGER")).toBeInTheDocument();
+ expect(screen.getByText("END")).toBeInTheDocument();
+ expect(screen.getByText("Continue to LLM")).toBeInTheDocument();
+ });
+
+ it("labels each decision section of a step", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByText("ON PASS")).toBeInTheDocument();
+ expect(screen.getByText("ON FAIL")).toBeInTheDocument();
+ expect(screen.getByText("ON API FAILURE")).toBeInTheDocument();
+ });
+
+ it("inserts a step at the clicked connector", async () => {
+ const onChange = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders(
+ ,
+ );
+
+ await user.click(screen.getAllByRole("button", { name: "Insert step" })[0]);
+
+ expect(onChange).toHaveBeenCalledTimes(1);
+ expect(onChange.mock.calls[0][0].steps).toHaveLength(2);
+ });
+
+ it("removes the clicked step when more than one exists", async () => {
+ const onChange = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders(
+ ,
+ );
+
+ await user.click(screen.getAllByRole("button", { name: "Delete step" })[0]);
+
+ expect(onChange.mock.calls[0][0].steps).toHaveLength(1);
+ expect(onChange.mock.calls[0][0].steps[0].guardrail).toBe("prompt-injection");
+ });
+
+ it("disables deletion of the only remaining step", () => {
+ renderWithProviders(
+ ,
+ );
+
+ expect(screen.getByRole("button", { name: "Delete step" })).toBeDisabled();
+ });
+
+ it("offers a custom response field only when the action is modify_response", () => {
+ const { rerender } = renderWithProviders(
+ ,
+ );
+ expect(screen.queryByPlaceholderText("Enter custom response...")).not.toBeInTheDocument();
+
+ rerender(
+ ,
+ );
+
+ expect(screen.getByPlaceholderText("Enter custom response...")).toBeInTheDocument();
+ });
+
+ it("reports an edited custom response message", async () => {
+ const onChange = vi.fn();
+ const user = userEvent.setup();
+ renderWithProviders(
+ ,
+ );
+
+ await user.type(screen.getByPlaceholderText("Enter custom response..."), "x");
+
+ expect(onChange.mock.calls[0][0].steps[0].modify_response_message).toBe("x");
+ });
+
+ it("offers a guardrail picker for the step", () => {
+ renderWithProviders(
+ ,
+ );
+
+ // Which control surfaces the selection is a presentation detail; that the step's
+ // guardrail is the one displayed is covered by the PipelineInfoDisplay tests above.
+ expect(screen.getByText("Guardrail")).toBeInTheDocument();
+ expect(screen.getAllByRole("combobox").length).toBeGreaterThan(0);
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/template_parameter_modal.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/template_parameter_modal.test.tsx
new file mode 100644
index 00000000000..1af45012d54
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/policies/_components/template_parameter_modal.test.tsx
@@ -0,0 +1,206 @@
+import React from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import TemplateParameterModal from "./template_parameter_modal";
+
+const { modelHubCall, enrichPolicyTemplateStream } = vi.hoisted(() => ({
+ modelHubCall: vi.fn(),
+ enrichPolicyTemplateStream: vi.fn(),
+}));
+
+vi.mock("@/components/networking", () => ({ modelHubCall, enrichPolicyTemplateStream }));
+
+type StreamResult = { competitors: string[]; competitor_variations?: Record };
+type StreamArgs = [
+ token: string,
+ templateId: string,
+ params: Record,
+ model: string,
+ onName: (name: string) => void,
+ onDone: (result: StreamResult) => void,
+];
+
+interface TestTemplate {
+ id: string;
+ title: string;
+ llm_enrichment?: { parameter: string };
+ parameters: { name: string; label: string; type: string; required: boolean; placeholder?: string }[];
+}
+
+const plainTemplate: TestTemplate = {
+ id: "tpl-plain",
+ title: "Basic Redaction",
+ parameters: [
+ { name: "org_name", label: "Organization Name", type: "string", required: true, placeholder: "e.g. Contoso" },
+ { name: "note", label: "Note", type: "string", required: false, placeholder: "optional note" },
+ ],
+};
+
+const enrichmentTemplate: TestTemplate = {
+ id: "tpl-competitor",
+ title: "Competitor Blocking",
+ llm_enrichment: { parameter: "brand_name" },
+ parameters: [{ name: "brand_name", label: "Your Brand Name", type: "string", required: true }],
+};
+
+const defaultProps = {
+ visible: true,
+ template: plainTemplate,
+ onConfirm: vi.fn(),
+ onCancel: vi.fn(),
+ accessToken: "sk-test",
+};
+
+const renderModal = (props: Partial = {}) =>
+ renderWithProviders();
+
+describe("TemplateParameterModal", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ modelHubCall.mockResolvedValue({ data: [{ model_group: "gpt-5.1" }] });
+ });
+
+ it("renders nothing while closed", () => {
+ renderModal({ visible: false });
+
+ expect(screen.queryByText("Basic Redaction")).not.toBeInTheDocument();
+ });
+
+ it("shows the template title and purpose when opened", async () => {
+ renderModal();
+
+ expect(await screen.findByText("Basic Redaction")).toBeInTheDocument();
+ expect(screen.getByText("Configure competitor blocking for your brand")).toBeInTheDocument();
+ });
+
+ it("renders a labelled field per template parameter and marks the required ones", async () => {
+ renderModal();
+
+ expect((await screen.findAllByText("Organization Name")).length).toBeGreaterThan(0);
+ expect(screen.getAllByText("Note").length).toBeGreaterThan(0);
+ expect(screen.getAllByPlaceholderText("e.g. Contoso").length).toBeGreaterThan(0);
+ expect(screen.getAllByPlaceholderText("optional note").length).toBeGreaterThan(0);
+ });
+
+ it("keeps Continue disabled until every required parameter is filled", async () => {
+ const user = userEvent.setup();
+ renderModal();
+
+ await screen.findByText("Basic Redaction");
+ expect(screen.getByRole("button", { name: "Continue" })).toBeDisabled();
+
+ await user.type(screen.getAllByPlaceholderText("e.g. Contoso")[0], "Contoso");
+
+ expect(screen.getByRole("button", { name: "Continue" })).not.toBeDisabled();
+ });
+
+ it("hands the entered parameters back to the caller", async () => {
+ const onConfirm = vi.fn();
+ const user = userEvent.setup();
+ renderModal({ onConfirm });
+
+ await screen.findByText("Basic Redaction");
+ await user.type(screen.getAllByPlaceholderText("e.g. Contoso")[0], "Contoso");
+ await user.click(screen.getByRole("button", { name: "Continue" }));
+
+ expect(onConfirm).toHaveBeenCalledTimes(1);
+ expect(onConfirm.mock.calls[0][0]).toEqual({ org_name: "Contoso", note: "" });
+ });
+
+ it("cancels back to the caller", async () => {
+ const onCancel = vi.fn();
+ const user = userEvent.setup();
+ renderModal({ onCancel });
+
+ await screen.findByText("Basic Redaction");
+ await user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(onCancel).toHaveBeenCalledTimes(1);
+ });
+
+ it("offers AI discovery controls for an enrichment template", async () => {
+ renderModal({ template: enrichmentTemplate });
+
+ expect(await screen.findByText("Competitor Discovery")).toBeInTheDocument();
+ expect(screen.getByText("Your Brand Name")).toBeInTheDocument();
+ expect(screen.getByText("Select Model")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /Generate Competitor Names/ })).toBeInTheDocument();
+ });
+
+ it("loads the model list for an enrichment template", async () => {
+ renderModal({ template: enrichmentTemplate });
+
+ await waitFor(() => {
+ expect(modelHubCall).toHaveBeenCalledWith("sk-test");
+ });
+ });
+
+ it("hides the model picker when competitors are entered manually", async () => {
+ const user = userEvent.setup();
+ renderModal({ template: enrichmentTemplate });
+
+ await screen.findByText("Competitor Discovery");
+ await user.click(screen.getByText("Enter Manually"));
+
+ await waitFor(() => {
+ expect(screen.queryByText("Select Model")).not.toBeInTheDocument();
+ });
+ expect(screen.queryByRole("button", { name: /Generate Competitor Names/ })).not.toBeInTheDocument();
+ });
+
+ it("keeps Continue disabled for an enrichment template until competitors exist", async () => {
+ const user = userEvent.setup();
+ renderModal({ template: enrichmentTemplate });
+
+ await screen.findByText("Competitor Discovery");
+ await user.type(screen.getByPlaceholderText("e.g. Acme Airlines"), "Contoso");
+
+ expect(screen.getByRole("button", { name: "Continue" })).toBeDisabled();
+ });
+
+ it("streams discovered competitor names and enables Continue once they arrive", async () => {
+ enrichPolicyTemplateStream.mockImplementation(async (...args: StreamArgs) => {
+ const [, , , , onName, onDone] = args;
+ onName("Northwind");
+ onDone({ competitors: ["Northwind", "Fabrikam"], competitor_variations: {} });
+ });
+ const user = userEvent.setup();
+ renderModal({ template: enrichmentTemplate });
+
+ await screen.findByText("Competitor Discovery");
+ await user.type(screen.getByPlaceholderText("e.g. Acme Airlines"), "Contoso");
+ await user.click(screen.getAllByRole("combobox")[0]);
+ const options = await screen.findAllByText("gpt-5.1");
+ await user.click(options[options.length - 1]);
+ await user.click(screen.getByRole("button", { name: /Generate Competitor Names/ }));
+
+ expect(await screen.findByText("Northwind")).toBeInTheDocument();
+ expect(screen.getByText("Fabrikam")).toBeInTheDocument();
+ await waitFor(() => {
+ expect(screen.getByRole("button", { name: "Continue" })).not.toBeDisabled();
+ });
+ });
+
+ it("passes the discovered competitors to the caller on confirm", async () => {
+ enrichPolicyTemplateStream.mockImplementation(async (...args: StreamArgs) => {
+ const [, , , , , onDone] = args;
+ onDone({ competitors: ["Northwind"] });
+ });
+ const onConfirm = vi.fn();
+ const user = userEvent.setup();
+ renderModal({ template: enrichmentTemplate, onConfirm });
+
+ await screen.findByText("Competitor Discovery");
+ await user.type(screen.getByPlaceholderText("e.g. Acme Airlines"), "Contoso");
+ await user.click(screen.getAllByRole("combobox")[0]);
+ const options = await screen.findAllByText("gpt-5.1");
+ await user.click(options[options.length - 1]);
+ await user.click(screen.getByRole("button", { name: /Generate Competitor Names/ }));
+ await screen.findByText("Northwind");
+ await user.click(screen.getByRole("button", { name: "Continue" }));
+
+ expect(onConfirm).toHaveBeenCalledWith({ brand_name: "Contoso" }, { competitors: ["Northwind"] });
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/email_events/email_event_settings.test.tsx b/ui/litellm-dashboard/src/components/email_events/email_event_settings.test.tsx
new file mode 100644
index 00000000000..b9737ccd5be
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/email_events/email_event_settings.test.tsx
@@ -0,0 +1,132 @@
+import React from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import EmailEventSettings from "./email_event_settings";
+
+const { getEmailEventSettings, updateEmailEventSettings, resetEmailEventSettings } = vi.hoisted(() => ({
+ getEmailEventSettings: vi.fn(),
+ updateEmailEventSettings: vi.fn(),
+ resetEmailEventSettings: vi.fn(),
+}));
+
+vi.mock("@/components/networking", () => ({
+ getEmailEventSettings,
+ updateEmailEventSettings,
+ resetEmailEventSettings,
+}));
+
+const settingsResponse = {
+ settings: [
+ { event: "Virtual Key Created", enabled: true },
+ { event: "New User Invitation", enabled: false },
+ ],
+};
+
+describe("EmailEventSettings", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ getEmailEventSettings.mockResolvedValue(settingsResponse);
+ updateEmailEventSettings.mockResolvedValue({});
+ resetEmailEventSettings.mockResolvedValue({});
+ });
+
+ it("renders the heading and the explanatory copy", async () => {
+ renderWithProviders();
+
+ expect(await screen.findByText("Email Notifications")).toBeInTheDocument();
+ expect(screen.getByText("Select which events should trigger email notifications.")).toBeInTheDocument();
+ });
+
+ it("renders one checkbox per event, reflecting the persisted enabled state", async () => {
+ renderWithProviders();
+
+ await screen.findByText("Virtual Key Created");
+ const checkboxes = screen.getAllByRole("checkbox");
+
+ expect(checkboxes).toHaveLength(2);
+ expect(checkboxes[0]).toBeChecked();
+ expect(checkboxes[1]).not.toBeChecked();
+ });
+
+ it("renders a per-event description", async () => {
+ renderWithProviders();
+
+ expect(
+ await screen.findByText(/An email will be sent to the user when a new virtual key is created/),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByText(/An email will be sent to the email address of the user when a new user is created/),
+ ).toBeInTheDocument();
+ });
+
+ it("saves the toggled enabled flags rather than the originally fetched ones", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await screen.findByText("Virtual Key Created");
+ await user.click(screen.getAllByRole("checkbox")[1]);
+ await user.click(screen.getByRole("button", { name: "Save Changes" }));
+
+ await waitFor(() => {
+ expect(updateEmailEventSettings).toHaveBeenCalledWith("sk-test", {
+ settings: [
+ { event: "Virtual Key Created", enabled: true },
+ { event: "New User Invitation", enabled: true },
+ ],
+ });
+ });
+ });
+
+ it("unchecking an enabled event is persisted as disabled", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await screen.findByText("Virtual Key Created");
+ await user.click(screen.getAllByRole("checkbox")[0]);
+ await user.click(screen.getByRole("button", { name: "Save Changes" }));
+
+ await waitFor(() => {
+ expect(updateEmailEventSettings).toHaveBeenCalledWith("sk-test", {
+ settings: [
+ { event: "Virtual Key Created", enabled: false },
+ { event: "New User Invitation", enabled: false },
+ ],
+ });
+ });
+ });
+
+ it("resets to defaults and refetches the settings", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await screen.findByText("Virtual Key Created");
+ expect(getEmailEventSettings).toHaveBeenCalledTimes(1);
+
+ await user.click(screen.getByRole("button", { name: "Reset to Defaults" }));
+
+ await waitFor(() => {
+ expect(resetEmailEventSettings).toHaveBeenCalledWith("sk-test");
+ });
+ await waitFor(() => {
+ expect(getEmailEventSettings).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ it("does not render event rows while the fetch is in flight", () => {
+ getEmailEventSettings.mockReturnValue(new Promise(() => {}));
+ renderWithProviders();
+
+ expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Save Changes" })).toBeDisabled();
+ });
+
+ it("does not call the API when there is no access token", async () => {
+ renderWithProviders();
+
+ await waitFor(() => {
+ expect(getEmailEventSettings).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/email_settings.test.tsx b/ui/litellm-dashboard/src/components/email_settings.test.tsx
new file mode 100644
index 00000000000..bd09ca0abc4
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/email_settings.test.tsx
@@ -0,0 +1,127 @@
+import React from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { screen, waitFor } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { renderWithProviders } from "@/../tests/test-utils";
+import EmailSettings from "./email_settings";
+
+const { serviceHealthCheck, setCallbacksCall } = vi.hoisted(() => ({
+ serviceHealthCheck: vi.fn(),
+ setCallbacksCall: vi.fn(),
+}));
+
+vi.mock("@/components/networking", () => ({ serviceHealthCheck, setCallbacksCall }));
+
+vi.mock("./email_events", () => ({
+ EmailEventSettings: () => email event settings
,
+}));
+
+const alerts = [
+ {
+ name: "email",
+ variables: {
+ SMTP_HOST: "smtp.example.com",
+ SMTP_PORT: "587",
+ SMTP_PASSWORD: "********",
+ EMAIL_LOGO_URL: "https://example.com/logo.png",
+ },
+ },
+ { name: "slack", variables: { SLACK_WEBHOOK_URL: "https://hooks.example.com" } },
+];
+
+const inputNamed = (name: string) => document.querySelector(`input[name="${name}"]`)!;
+
+describe("EmailSettings", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ setCallbacksCall.mockResolvedValue({});
+ serviceHealthCheck.mockResolvedValue({});
+ });
+
+ it("renders the heading and the docs link", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("Email Server Settings")).toBeInTheDocument();
+ expect(screen.getByRole("link", { name: /LiteLLM Docs: email alerts/ })).toHaveAttribute(
+ "href",
+ "https://docs.litellm.ai/docs/proxy/email",
+ );
+ });
+
+ it("renders one named input per email variable and none for other alert types", () => {
+ renderWithProviders();
+
+ expect(inputNamed("SMTP_HOST")).toHaveValue("smtp.example.com");
+ expect(inputNamed("SMTP_PORT")).toHaveValue("587");
+ expect(inputNamed("SMTP_PASSWORD")).toHaveValue("********");
+ expect(document.querySelector('input[name="SLACK_WEBHOOK_URL"]')).toBeNull();
+ });
+
+ it("labels each variable and shows its help text", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("SMTP_HOST")).toBeInTheDocument();
+ expect(screen.getByText(/Enter the SMTP host address/)).toBeInTheDocument();
+ expect(screen.getByText(/Enter the SMTP port number/)).toBeInTheDocument();
+ });
+
+ it("submits only the fields the admin actually edited", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await user.clear(inputNamed("SMTP_HOST"));
+ await user.type(inputNamed("SMTP_HOST"), "smtp.changed.com");
+ await user.click(screen.getByRole("button", { name: "Save Changes" }));
+
+ await waitFor(() => {
+ expect(setCallbacksCall).toHaveBeenCalledWith("sk-test", {
+ general_settings: { alerting: ["email"] },
+ environment_variables: { SMTP_HOST: "smtp.changed.com" },
+ });
+ });
+ });
+
+ it("does not resubmit an untouched masked value", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await user.click(screen.getByRole("button", { name: "Save Changes" }));
+
+ await waitFor(() => {
+ expect(setCallbacksCall).toHaveBeenCalledWith("sk-test", {
+ general_settings: { alerting: ["email"] },
+ environment_variables: {},
+ });
+ });
+ });
+
+ it("disables the premium-only fields for non-premium users", () => {
+ renderWithProviders();
+
+ expect(inputNamed("EMAIL_LOGO_URL")).toBeDisabled();
+ expect(inputNamed("SMTP_HOST")).not.toBeDisabled();
+ });
+
+ it("leaves the premium-only fields editable for premium users", () => {
+ renderWithProviders();
+
+ expect(inputNamed("EMAIL_LOGO_URL")).not.toBeDisabled();
+ });
+
+ it("triggers a live email health check", async () => {
+ const user = userEvent.setup();
+ renderWithProviders();
+
+ await user.click(screen.getByRole("button", { name: "Test Email Alerts" }));
+
+ await waitFor(() => {
+ expect(serviceHealthCheck).toHaveBeenCalledWith("sk-test", "email");
+ });
+ });
+
+ it("renders the email event settings section", () => {
+ renderWithProviders();
+
+ expect(screen.getByText("email event settings")).toBeInTheDocument();
+ });
+});