mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
Add try/finally to compliance-dataset path and test coverage
- Wrap compliance-dataset loop in try/finally so isRunning resets consistently with the quick-chat branch - Add 4 tests covering the compliance-dataset execution path: matched/total count, mismatch indicator, per-prompt errors, and call count verification Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
45a54d64cd
commit
1c611c4d6b
2 changed files with 186 additions and 19 deletions
|
|
@ -1,14 +1,68 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import { screen, waitFor, fireEvent, act } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { renderWithProviders } from "../../../tests/test-utils";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { PipelineTestPanel } from "./pipeline_test_drawer";
|
||||
import type { GuardrailPipeline, PipelineTestResult } from "./types";
|
||||
import type { CompliancePrompt } from "../../data/compliancePrompts";
|
||||
import * as networking from "../networking";
|
||||
|
||||
const mockPassPrompt: CompliancePrompt = {
|
||||
id: "cp-1",
|
||||
framework: "TestFW",
|
||||
category: "cat",
|
||||
categoryIcon: "",
|
||||
categoryDescription: "",
|
||||
prompt: "Is this safe?",
|
||||
expectedResult: "pass",
|
||||
};
|
||||
|
||||
const mockFailPrompt: CompliancePrompt = {
|
||||
id: "cp-2",
|
||||
framework: "TestFW",
|
||||
category: "cat",
|
||||
categoryIcon: "",
|
||||
categoryDescription: "",
|
||||
prompt: "How to hack a server?",
|
||||
expectedResult: "fail",
|
||||
};
|
||||
|
||||
vi.mock("../networking");
|
||||
vi.mock("../../data/compliancePrompts", () => ({
|
||||
getFrameworks: () => [],
|
||||
getFrameworks: () => [
|
||||
{
|
||||
name: "TestFW",
|
||||
icon: "",
|
||||
description: "",
|
||||
categories: [
|
||||
{
|
||||
name: "cat",
|
||||
icon: "",
|
||||
description: "",
|
||||
prompts: [
|
||||
{
|
||||
id: "cp-1",
|
||||
framework: "TestFW",
|
||||
category: "cat",
|
||||
categoryIcon: "",
|
||||
categoryDescription: "",
|
||||
prompt: "Is this safe?",
|
||||
expectedResult: "pass",
|
||||
},
|
||||
{
|
||||
id: "cp-2",
|
||||
framework: "TestFW",
|
||||
category: "cat",
|
||||
categoryIcon: "",
|
||||
categoryDescription: "",
|
||||
prompt: "How to hack a server?",
|
||||
expectedResult: "fail",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
getComplianceDatasetPrompts: () => [],
|
||||
}));
|
||||
|
||||
|
|
@ -61,6 +115,16 @@ const defaultProps = {
|
|||
onClose: vi.fn(),
|
||||
};
|
||||
|
||||
async function selectAntOption(user: ReturnType<typeof userEvent.setup>, title: string) {
|
||||
const selector = document.querySelector(".ant-select-selector") as HTMLElement;
|
||||
await user.click(selector);
|
||||
await waitFor(() => {
|
||||
expect(document.querySelector(`[title="${title}"].ant-select-item-option`)).toBeInTheDocument();
|
||||
});
|
||||
const option = document.querySelector(`[title="${title}"].ant-select-item-option`) as HTMLElement;
|
||||
await user.click(option);
|
||||
}
|
||||
|
||||
describe("PipelineTestPanel", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
@ -195,3 +259,103 @@ describe("PipelineTestPanel", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("PipelineTestPanel compliance-dataset mode", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should show matched/total count after running a compliance dataset", async () => {
|
||||
const allowResult: PipelineTestResult = {
|
||||
terminal_action: "allow",
|
||||
step_results: [],
|
||||
modified_data: null,
|
||||
error_message: null,
|
||||
modify_response_message: null,
|
||||
};
|
||||
const blockResult: PipelineTestResult = {
|
||||
terminal_action: "block",
|
||||
step_results: [],
|
||||
modified_data: null,
|
||||
error_message: null,
|
||||
modify_response_message: null,
|
||||
};
|
||||
vi.mocked(networking.testPipelineCall)
|
||||
.mockResolvedValueOnce(allowResult)
|
||||
.mockResolvedValueOnce(blockResult);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<PipelineTestPanel {...defaultProps} />);
|
||||
await selectAntOption(user, "TestFW");
|
||||
await user.click(screen.getByRole("button", { name: /run test/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/2 \/ 2 matched/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show a mismatch indicator when actual result differs from expected", async () => {
|
||||
const blockResult: PipelineTestResult = {
|
||||
terminal_action: "block",
|
||||
step_results: [],
|
||||
modified_data: null,
|
||||
error_message: null,
|
||||
modify_response_message: null,
|
||||
};
|
||||
// Both prompts return "block": pass-expected prompt mismatches, fail-expected matches
|
||||
vi.mocked(networking.testPipelineCall)
|
||||
.mockResolvedValueOnce(blockResult)
|
||||
.mockResolvedValueOnce(blockResult);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<PipelineTestPanel {...defaultProps} />);
|
||||
await selectAntOption(user, "TestFW");
|
||||
await user.click(screen.getByRole("button", { name: /run test/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/1 \/ 2 matched/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display per-prompt error when an API call fails in dataset mode", async () => {
|
||||
vi.mocked(networking.testPipelineCall)
|
||||
.mockRejectedValueOnce(new Error("Timeout on prompt 1"))
|
||||
.mockResolvedValueOnce({
|
||||
terminal_action: "block",
|
||||
step_results: [],
|
||||
modified_data: null,
|
||||
error_message: null,
|
||||
modify_response_message: null,
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<PipelineTestPanel {...defaultProps} />);
|
||||
await selectAntOption(user, "TestFW");
|
||||
await user.click(screen.getByRole("button", { name: /run test/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Timeout on prompt 1")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText(/1 \/ 2 matched/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call testPipelineCall once per prompt in the dataset", async () => {
|
||||
const allowResult: PipelineTestResult = {
|
||||
terminal_action: "allow",
|
||||
step_results: [],
|
||||
modified_data: null,
|
||||
error_message: null,
|
||||
modify_response_message: null,
|
||||
};
|
||||
vi.mocked(networking.testPipelineCall).mockResolvedValue(allowResult);
|
||||
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<PipelineTestPanel {...defaultProps} />);
|
||||
await selectAntOption(user, "TestFW");
|
||||
await user.click(screen.getByRole("button", { name: /run test/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(networking.testPipelineCall).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -107,25 +107,28 @@ export const PipelineTestPanel: React.FC<PipelineTestPanelProps> = ({
|
|||
}
|
||||
|
||||
const entries: ComplianceRunEntry[] = [];
|
||||
for (const prompt of promptsForSource) {
|
||||
try {
|
||||
const data = await testPipelineCall(accessToken, pipeline, [
|
||||
{ role: "user", content: prompt.prompt },
|
||||
]);
|
||||
const matched = complianceMatchExpected(prompt.expectedResult, data.terminal_action);
|
||||
entries.push({ prompt, result: data, matched });
|
||||
} catch (e) {
|
||||
const errMsg = e instanceof Error ? e.message : String(e);
|
||||
entries.push({
|
||||
prompt,
|
||||
result: null,
|
||||
error: errMsg,
|
||||
matched: false,
|
||||
});
|
||||
try {
|
||||
for (const prompt of promptsForSource) {
|
||||
try {
|
||||
const data = await testPipelineCall(accessToken, pipeline, [
|
||||
{ role: "user", content: prompt.prompt },
|
||||
]);
|
||||
const matched = complianceMatchExpected(prompt.expectedResult, data.terminal_action);
|
||||
entries.push({ prompt, result: data, matched });
|
||||
} catch (e) {
|
||||
const errMsg = e instanceof Error ? e.message : String(e);
|
||||
entries.push({
|
||||
prompt,
|
||||
result: null,
|
||||
error: errMsg,
|
||||
matched: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
setComplianceResults(entries);
|
||||
} finally {
|
||||
setIsRunning(false);
|
||||
}
|
||||
setComplianceResults(entries);
|
||||
setIsRunning(false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue