feat: complete catalog fleet management workflow
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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();
|
||||
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();
|
||||
});
|
||||
Reference in New Issue
Block a user