test(ui): pin logging-and-alerts, caching and policies behaviour before the shadcn migration

Establishes the regression net for the upcoming markup migration of these
three routes. Every assertion here is written against the current antd and
Tremor components and passes against them, so it carries no knowledge of the
markup that replaces them and stays meaningful afterwards.

Adds characterisation tests for the seven components that had none, and
rewrites cache_dashboard's chart-card lookup to anchor on each chart's own
title instead of asserting a global count of card nodes, which would break the
moment another card appears on the page.

No component is touched in this commit.
This commit is contained in:
Yuneng Jiang 2026-07-23 22:28:37 -07:00
parent 64aad5877a
commit 33dc162893
No known key found for this signature in database
8 changed files with 1081 additions and 4 deletions

View file

@ -40,14 +40,23 @@ const renderDashboard = () =>
<CacheDashboard accessToken="sk-test" token="tok" userRole="Admin" userID="u1" premiumUser={false} />,
);
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) =>

View file

@ -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<React.ComponentProps<typeof CacheHealthTab>> = {}) =>
renderWithProviders(
<CacheHealthTab
{...{ accessToken: "sk-test", healthCheckResponse: "", runCachingHealthCheck: vi.fn(), ...overrides }}
/>,
);
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<void>(() => {}));
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();
});
});

View file

@ -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(<CoordinationRedisTypeSelector redisType="node" onTypeChange={vi.fn()} />);
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(<CoordinationRedisTypeSelector redisType="cluster" onTypeChange={vi.fn()} />);
expect(screen.getByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.cluster)).toBeInTheDocument();
});
it("switches the description when the selected type changes", () => {
const { rerender } = renderWithProviders(<CoordinationRedisTypeSelector redisType="node" onTypeChange={vi.fn()} />);
expect(screen.getByText(COORDINATION_REDIS_TYPE_DESCRIPTIONS.node)).toBeInTheDocument();
rerender(<CoordinationRedisTypeSelector redisType="sentinel" onTypeChange={vi.fn()} />);
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(<CoordinationRedisTypeSelector redisType="node" onTypeChange={onTypeChange} />);
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(<CoordinationRedisTypeSelector redisType="node" onTypeChange={vi.fn()} />);
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);
});
});

View file

@ -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<typeof defaultProps> = {}) =>
renderWithProviders(<AiSuggestionModal {...defaultProps} {...props} />);
const pickModel = async (user: ReturnType<typeof userEvent.setup>) => {
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);
});
});

View file

@ -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> = {}): 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(<PipelineInfoDisplay pipeline={pipeline([step()])} />);
expect(screen.getByText("TRIGGER")).toBeInTheDocument();
expect(screen.getByText("Incoming LLM Request")).toBeInTheDocument();
});
it("renders one numbered card per step, naming its guardrail", () => {
renderWithProviders(<PipelineInfoDisplay pipeline={pipeline([step(), step({ guardrail: "prompt-injection" })])} />);
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(<PipelineInfoDisplay pipeline={pipeline([step({ on_pass: "next", on_fail: "block" })])} />);
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(<PipelineInfoDisplay pipeline={pipeline([step({ on_fail: "block", on_error: null })])} />);
expect(screen.getByText(/On API failure .* Block \(same as on fail\)/)).toBeInTheDocument();
});
it("shows an explicit API-failure action when one is set", () => {
renderWithProviders(<PipelineInfoDisplay pipeline={pipeline([step({ on_error: "allow" })])} />);
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(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={vi.fn()} availableGuardrails={guardrails} />,
);
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(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={vi.fn()} availableGuardrails={guardrails} />,
);
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(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={onChange} availableGuardrails={guardrails} />,
);
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(
<PipelineFlowBuilder
pipeline={pipeline([step(), step({ guardrail: "prompt-injection" })])}
onChange={onChange}
availableGuardrails={guardrails}
/>,
);
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(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={vi.fn()} availableGuardrails={guardrails} />,
);
expect(screen.getByRole("button", { name: "Delete step" })).toBeDisabled();
});
it("offers a custom response field only when the action is modify_response", () => {
const { rerender } = renderWithProviders(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={vi.fn()} availableGuardrails={guardrails} />,
);
expect(screen.queryByPlaceholderText("Enter custom response...")).not.toBeInTheDocument();
rerender(
<PipelineFlowBuilder
pipeline={pipeline([step({ on_fail: "modify_response" })])}
onChange={vi.fn()}
availableGuardrails={guardrails}
/>,
);
expect(screen.getByPlaceholderText("Enter custom response...")).toBeInTheDocument();
});
it("reports an edited custom response message", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
renderWithProviders(
<PipelineFlowBuilder
pipeline={pipeline([step({ on_fail: "modify_response" })])}
onChange={onChange}
availableGuardrails={guardrails}
/>,
);
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(
<PipelineFlowBuilder pipeline={pipeline([step()])} onChange={vi.fn()} availableGuardrails={guardrails} />,
);
// 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);
});
});

View file

@ -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<string, string[]> };
type StreamArgs = [
token: string,
templateId: string,
params: Record<string, string>,
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<typeof defaultProps> = {}) =>
renderWithProviders(<TemplateParameterModal {...defaultProps} {...props} />);
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"] });
});
});

View file

@ -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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken="sk-test" />);
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(<EmailEventSettings accessToken={null} />);
await waitFor(() => {
expect(getEmailEventSettings).not.toHaveBeenCalled();
});
});
});

View file

@ -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: () => <div>email event settings</div>,
}));
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<HTMLInputElement>(`input[name="${name}"]`)!;
describe("EmailSettings", () => {
beforeEach(() => {
vi.clearAllMocks();
setCallbacksCall.mockResolvedValue({});
serviceHealthCheck.mockResolvedValue({});
});
it("renders the heading and the docs link", () => {
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser={false} alerts={alerts} />);
expect(inputNamed("EMAIL_LOGO_URL")).toBeDisabled();
expect(inputNamed("SMTP_HOST")).not.toBeDisabled();
});
it("leaves the premium-only fields editable for premium users", () => {
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
expect(inputNamed("EMAIL_LOGO_URL")).not.toBeDisabled();
});
it("triggers a live email health check", async () => {
const user = userEvent.setup();
renderWithProviders(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
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(<EmailSettings accessToken="sk-test" premiumUser alerts={alerts} />);
expect(screen.getByText("email event settings")).toBeInTheDocument();
});
});