138 lines
5.4 KiB
TypeScript
138 lines
5.4 KiB
TypeScript
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { http, HttpResponse } from "msw";
|
|
import { useState } from "react";
|
|
import type { SensitiveDataSuggestionRun } from "../../api/catalog-databases";
|
|
import { server } from "../../test/msw";
|
|
import { SensitiveDataSuggestionHistoryDrawer } from "./SensitiveDataSuggestionHistoryDrawer";
|
|
|
|
const runningRun: SensitiveDataSuggestionRun = {
|
|
id: "99999999-9999-4999-8999-999999999999",
|
|
databaseId: "11111111-1111-4111-8111-111111111111",
|
|
scope: "selected_columns",
|
|
modelId: "local-qwen",
|
|
status: "running",
|
|
total: 4,
|
|
suggestedSensitive: 1,
|
|
suggestedNonSensitive: 1,
|
|
createdAt: "2026-08-28T09:00:00Z",
|
|
startedAt: "2026-08-28T09:00:00Z",
|
|
updatedAt: "2026-08-28T09:00:01Z",
|
|
finishedAt: null,
|
|
errorSummary: null,
|
|
};
|
|
|
|
function renderDrawer(initialRun: SensitiveDataSuggestionRun | null) {
|
|
const client = new QueryClient({
|
|
defaultOptions: { queries: { retry: false, gcTime: Infinity } },
|
|
});
|
|
|
|
function Harness() {
|
|
const [run, setRun] = useState(initialRun);
|
|
return (
|
|
<SensitiveDataSuggestionHistoryDrawer
|
|
open
|
|
run={run}
|
|
modelLabel="Local Qwen"
|
|
onClose={() => undefined}
|
|
onRunUpdate={setRun}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<Harness />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
test("shows an explicit empty state before any sensitive suggestion runs exist", async () => {
|
|
server.use(http.get(
|
|
"/api/catalog/sensitive-data-suggestion-runs",
|
|
() => HttpResponse.json([]),
|
|
));
|
|
|
|
renderDrawer(null);
|
|
|
|
const drawer = await screen.findByRole("dialog", { name: "Sensitive suggestion history" });
|
|
expect(within(drawer).getByRole("heading", { name: "Suggestion run history" })).toBeVisible();
|
|
expect(await within(drawer).findByText("No sensitive suggestion runs yet.")).toBeVisible();
|
|
});
|
|
|
|
test("shows sensitive suggestion results, safe events, and lets operators inspect an older run", async () => {
|
|
const completedRun: SensitiveDataSuggestionRun = {
|
|
...runningRun,
|
|
id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
|
|
scope: "all",
|
|
status: "completed",
|
|
total: 6,
|
|
suggestedSensitive: 2,
|
|
suggestedNonSensitive: 4,
|
|
createdAt: "2026-08-28T08:00:00Z",
|
|
startedAt: "2026-08-28T08:00:00Z",
|
|
updatedAt: "2026-08-28T08:00:01Z",
|
|
finishedAt: "2026-08-28T08:00:01Z",
|
|
};
|
|
server.use(
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs", () => (
|
|
HttpResponse.json([runningRun, completedRun])
|
|
)),
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs/:runId", ({ params }) => (
|
|
HttpResponse.json(params.runId === completedRun.id ? completedRun : runningRun)
|
|
)),
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs/:runId/events-list", ({ params }) => (
|
|
HttpResponse.json([{
|
|
runId: params.runId,
|
|
sequence: 1,
|
|
level: "info",
|
|
message: params.runId === completedRun.id ? "Classified 6 columns" : "Classifying columns",
|
|
createdAt: "2026-08-28T09:00:01Z",
|
|
}])
|
|
)),
|
|
);
|
|
const user = userEvent.setup();
|
|
renderDrawer(runningRun);
|
|
|
|
const drawer = await screen.findByRole("dialog", { name: "Sensitive suggestion history" });
|
|
expect(within(drawer).getByRole("heading", { name: "Running" })).toBeVisible();
|
|
const progress = within(drawer).getByRole("table", { name: "Sensitive suggestion progress" });
|
|
expect(within(progress).getAllByRole("columnheader")).toHaveLength(3);
|
|
expect(within(drawer).getByRole("log", { name: "Sensitive suggestion events" })).toHaveClass(
|
|
"thot-catalog-drawer__event-log",
|
|
);
|
|
expect(await within(drawer).findByText("Classifying columns")).toBeVisible();
|
|
expect(within(drawer).queryByRole("button", { name: "Stop" })).not.toBeInTheDocument();
|
|
expect(within(drawer).queryByRole("button", { name: "Unlock stale run" })).not.toBeInTheDocument();
|
|
|
|
const history = within(drawer).getByRole("region", { name: "Sensitive suggestion run history" });
|
|
await user.click(within(history).getByRole("button", { name: /Completed.*all/i }));
|
|
|
|
expect(await within(drawer).findByRole("heading", { name: "Completed" })).toBeVisible();
|
|
expect(within(drawer).getByText("2", { selector: "[data-count='suggestedSensitive']" })).toBeVisible();
|
|
expect(within(drawer).getByText("4", { selector: "[data-count='suggestedNonSensitive']" })).toBeVisible();
|
|
await waitFor(() => expect(within(drawer).getByText("Classified 6 columns")).toBeVisible());
|
|
});
|
|
|
|
test("does not expose unsafe provider details from a final error summary", async () => {
|
|
const failedRun: SensitiveDataSuggestionRun = {
|
|
...runningRun,
|
|
status: "failed",
|
|
finishedAt: "2026-08-28T09:00:02Z",
|
|
errorSummary: "stack trace at provider.ts:42 private source sample sk-secret",
|
|
};
|
|
server.use(
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs", () => HttpResponse.json([failedRun])),
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs/:runId", () => HttpResponse.json(failedRun)),
|
|
http.get("/api/catalog/sensitive-data-suggestion-runs/:runId/events-list", () => HttpResponse.json([])),
|
|
);
|
|
|
|
renderDrawer(failedRun);
|
|
|
|
expect(await screen.findByText(
|
|
"The run ended before all columns were classified. Review the event log for safe details.",
|
|
)).toBeVisible();
|
|
expect(screen.queryByText(/sk-secret/i)).not.toBeInTheDocument();
|
|
});
|