feat: complete catalog fleet management workflow

This commit is contained in:
Codex
2026-08-31 15:58:43 +02:00
parent 866aee4249
commit 9c697dc062
56 changed files with 6692 additions and 611 deletions
@@ -9,6 +9,7 @@ import type {
CatalogSyncRun,
CatalogTable,
DescriptionGenerationRun,
SensitiveDataSuggestionRun,
} from "../api/catalog-databases";
import { Toaster } from "../components/ui/sonner";
import { DatabaseManagementPage } from "./DatabaseManagementPage";
@@ -72,11 +73,13 @@ function renderPage({
canManage = true,
canManageSecrets = true,
onNavigationStateChange,
presentation = "legacy",
}: {
rows?: CatalogDatabase[] | (() => CatalogDatabase[]);
canManage?: boolean;
canManageSecrets?: boolean;
onNavigationStateChange?: (state: { dirty: boolean; busy: boolean }) => void;
presentation?: "legacy" | "fleet";
} = {}) {
server.use(http.get("/api/catalog/databases", () => HttpResponse.json(
typeof rows === "function" ? rows() : rows,
@@ -88,6 +91,7 @@ function renderPage({
canManage={canManage}
canManageSecrets={canManageSecrets}
onNavigationStateChange={onNavigationStateChange}
presentation={presentation}
/>
<Toaster duration={Infinity} />
</QueryClientProvider>,
@@ -145,6 +149,27 @@ function makeDescriptionGenerationRun(
};
}
function makeSensitiveDataSuggestionRun(
overrides: Partial<SensitiveDataSuggestionRun> = {},
): SensitiveDataSuggestionRun {
return {
id: "99999999-9999-4999-8999-999999999999",
databaseId: "11111111-1111-4111-8111-111111111111",
modelId: "local-qwen",
scope: "selected_columns",
status: "completed",
total: 2,
suggestedSensitive: 1,
suggestedNonSensitive: 1,
createdAt: "2026-08-28T11:00:00Z",
startedAt: "2026-08-28T11:00:00Z",
updatedAt: "2026-08-28T11:00:01Z",
finishedAt: "2026-08-28T11:00:01Z",
errorSummary: null,
...overrides,
};
}
function registerCompletedSyncRun(run: CatalogSyncRun) {
const completed: CatalogSyncRun = {
...run,
@@ -167,6 +192,116 @@ const synchronizationScopes = [
{ scope: "all", label: "Synchronize all" },
] as const;
test("renders Fleet Ledger with real metrics, conceptual row tooltips, and contextual model selection", async () => {
const user = userEvent.setup();
server.use(
http.get("/api/catalog/metrics", () => HttpResponse.json({
scope: "global",
databaseId: null,
tables: 163,
columns: 2_275,
sensitiveColumns: 5,
relationships: 18,
descriptionTargets: 2_438,
describedTargets: 1_828,
descriptionCoverage: 75,
updatedAt: "2026-08-31T09:00:00Z",
})),
http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
models: [{ id: "local-qwen", label: "Local Qwen" }],
default: "local-qwen",
})),
);
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
const pageHeading = await screen.findByRole("heading", { name: "Database management" });
expect(pageHeading).toBeVisible();
const applicationBar = pageHeading.closest("header");
expect(applicationBar).not.toBeNull();
expect(within(applicationBar!).getByRole("region", { name: "Catalog status" })).toBeVisible();
expect(within(applicationBar!).getByRole("group", { name: "Database management actions" })).toBeVisible();
expect(screen.queryByText("Thoth catalog · Fleet ledger")).not.toBeInTheDocument();
expect(screen.queryByText("Inspect physical metadata, curate descriptions and control catalog operations.")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Back to workspace" })).not.toBeInTheDocument();
const summary = screen.getByRole("region", { name: "Fleet summary" });
expect(await within(summary).findByText("2,275")).toBeVisible();
expect(within(summary).getByText("75%")).toBeVisible();
expect(screen.queryByRole("combobox", { name: "Metadata description model" })).not.toBeInTheDocument();
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
const tablesAction = within(databaseRow).getByRole("button", { name: "View tables for Policlinico San Donato" });
const detailsAction = within(databaseRow).getByRole("button", { name: "View Policlinico San Donato" });
expect(tablesAction.parentElement).toHaveAttribute("data-tooltip", "Tables");
expect(tablesAction).not.toHaveAttribute("title");
expect(detailsAction).toHaveAttribute("data-tooltip", "Details");
const manager = screen.getByRole("main", { name: "Database management" });
await user.click(within(databaseRow).getByRole("button", { name: "Edit Policlinico San Donato" }));
const editDrawer = await screen.findByRole("dialog", { name: "Edit database" });
expect(manager).toContainElement(editDrawer);
expect(editDrawer).toHaveAttribute("aria-modal", "false");
await user.click(within(editDrawer).getByRole("button", { name: "Close database panel" }));
const restoredDatabaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(restoredDatabaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
const actionPicker = screen.getByRole("combobox", { name: "Batch action" });
expect(within(actionPicker).getByRole("option", { name: /^Synchronize all(?:,|$)/ })).toBeVisible();
await user.selectOptions(actionPicker, "generate-missing");
expect(await screen.findByRole("combobox", { name: "Metadata description model" })).toHaveValue("local-qwen");
});
test("keeps only the local back action while browsing Fleet Ledger columns", async () => {
const user = userEvent.setup();
server.use(
http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])),
http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([])),
);
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("button", {
name: "View tables for Policlinico San Donato",
}));
expect(await screen.findByRole("button", { name: "Back to databases" })).toBeVisible();
await user.click(await screen.findByRole("button", { name: "View columns for patients" }));
expect(await screen.findByRole("button", { name: "Back to tables" })).toBeVisible();
await waitFor(() => {
expect(screen.queryByRole("button", { name: "Back to databases" })).not.toBeInTheDocument();
});
await user.click(screen.getByRole("button", { name: "Back to tables" }));
expect(await screen.findByRole("button", { name: "Back to databases" })).toBeVisible();
expect(screen.queryByRole("button", { name: "Back to tables" })).not.toBeInTheDocument();
});
test("reopens database synchronization history when no run is active", async () => {
const user = userEvent.setup();
const completed: CatalogSyncRun = {
...makeSyncRun("all"),
state: "succeeded",
phase: "completed",
startedAt: "2026-08-27T10:00:00Z",
finishedAt: "2026-08-27T10:00:01Z",
};
server.use(
http.get("/api/catalog/databases/:databaseId/sync-runs", () => HttpResponse.json([completed])),
http.get("/api/catalog/sync-runs/:runId", () => HttpResponse.json(completed)),
http.get("/api/catalog/sync-runs/:runId/events-list", () => HttpResponse.json([])),
);
renderPage({ rows: [makeDatabase()], presentation: "fleet" });
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("button", { name: "View synchronization history for Policlinico San Donato" }));
const drawer = await screen.findByRole("dialog", { name: "Schema synchronization" });
expect(drawer).toBeVisible();
expect(screen.getByRole("main", { name: "Database management" })).toContainElement(drawer);
expect(await screen.findByRole("heading", { name: "Succeeded" })).toBeVisible();
expect(screen.getByRole("region", { name: "Synchronization history" })).toBeVisible();
});
test("selects the configured metadata-description model by default", async () => {
server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
models: [
@@ -181,6 +316,50 @@ test("selects the configured metadata-description model by default", async () =>
await waitFor(() => expect(selector).toHaveValue("local-qwen"));
});
test("keeps both run-history buttons visible beside the metadata-description selector", async () => {
const user = userEvent.setup();
renderPage();
const selector = await screen.findByRole("combobox", { name: "Metadata description model" });
const toolbar = screen.getByRole("group", { name: "Database management controls" });
const metadataControls = screen.getByRole("group", { name: "Metadata description controls" });
const actions = screen.getByRole("group", { name: "Database management actions" });
const descriptionHistoryButton = within(metadataControls).getByRole("button", {
name: "Run descriptions generation history",
});
const suggestionHistoryButton = within(metadataControls).getByRole("button", {
name: "Run sensitive suggestions history",
});
expect(toolbar).toHaveClass("sm:items-end", "sm:justify-between");
expect(toolbar.firstElementChild).toBe(metadataControls);
expect(metadataControls).toContainElement(selector);
expect(descriptionHistoryButton).toBeVisible();
expect(descriptionHistoryButton).toBeEnabled();
expect(descriptionHistoryButton).toHaveTextContent("Run descriptions generation history");
expect(descriptionHistoryButton).toHaveClass("disabled:opacity-70");
expect(suggestionHistoryButton).toBeVisible();
expect(suggestionHistoryButton).toBeEnabled();
expect(suggestionHistoryButton).toHaveTextContent("Run sensitive suggestions history");
expect(toolbar.lastElementChild).toBe(actions);
expect(actions).toHaveClass("sm:justify-end");
expect(actions).toContainElement(screen.getByRole("button", { name: "Refresh" }));
expect(actions).toContainElement(screen.getByRole("button", { name: "Add database" }));
expect(screen.queryByRole("note", {
name: "Metadata generation source data disclosure",
})).not.toBeInTheDocument();
expect(document.getElementById("metadata-generation-source-data-disclosure")).toBeNull();
await user.click(descriptionHistoryButton);
const descriptionDrawer = await screen.findByRole("dialog", { name: "Description generation" });
expect(within(descriptionDrawer).getByText("No description generation runs yet.")).toBeVisible();
await user.click(suggestionHistoryButton);
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
const suggestionDrawer = await screen.findByRole("dialog", { name: "Sensitive suggestion history" });
expect(within(suggestionDrawer).getByText("No sensitive suggestion runs yet.")).toBeVisible();
});
test("changes the metadata-description model in page-local state", async () => {
const user = userEvent.setup();
server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
@@ -271,38 +450,6 @@ test("renders only the public metadata-generation model contract", async () => {
expect(document.body).not.toHaveTextContent("raw-private-credential");
});
test("discloses bounded transient source samples in database-wide and selected generation views", async () => {
const user = userEvent.setup();
server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
models: [{ id: "local-qwen", label: "Local Qwen" }],
default: "local-qwen",
})), http.get(
"/api/catalog/databases/:databaseId/tables",
() => HttpResponse.json([]),
));
renderPage();
const disclosure = await screen.findByRole("note", {
name: "Metadata generation source data disclosure",
});
expect(within(disclosure).getByText(
"Description generation may send up to five source rows and up to five representative non-null example values to the selected model provider. Values from columns marked sensitive are replaced with plausible synthetic values before the request.",
)).toBeVisible();
expect(within(disclosure).getByText(
"Values from unmarked columns may be sent unchanged. Samples are transient and are not stored in run logs or catalog metadata.",
)).toBeVisible();
expect(within(disclosure).getByText(
"Sensitive-field suggestions use structural metadata only and remain unsaved until you choose Save sensitive fields.",
)).toBeVisible();
await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" }));
await user.click(screen.getByRole("tab", { name: "Tables" }));
expect(screen.getByRole("note", {
name: "Metadata generation source data disclosure",
})).toBeVisible();
});
test("starts with a full-width list and applies the row action matrix", async () => {
renderPage();
@@ -377,7 +524,7 @@ test("starts Generate Missing for one configured database without confirmation",
scope: "missing",
}));
expect(screen.queryByText(/replace existing generated descriptions/i)).not.toBeInTheDocument();
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
});
test("confirms Generate All replacement, supports cancel, and sends the database-wide scope", async () => {
@@ -419,7 +566,7 @@ test("confirms Generate All replacement, supports cancel, and sends the database
modelId: "local-qwen",
scope: "all",
}));
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
});
test("keeps the database selected and safely explains when no descriptions are eligible", async () => {
@@ -445,7 +592,7 @@ test("keeps the database selected and safely explains when no descriptions are e
"No eligible catalog tables or columns need description generation.",
)).toBeVisible();
expect(screen.getByText("1 selected")).toBeVisible();
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
expect(document.body).not.toHaveTextContent("private provider and catalog detail");
});
@@ -605,7 +752,7 @@ test("disables database-wide generation while a description generation is active
await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Generate Missing" }));
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
@@ -1177,7 +1324,7 @@ test("starts description generation for multiple selected tables", async () => {
scope: "selected_tables",
targetIds: [patientsTable.id, visitsTable.id],
}));
expect(await screen.findByRole("complementary", {
expect(await screen.findByRole("dialog", {
name: "Description generation",
})).toBeVisible();
expect(await screen.findByText("Description generation started for 2 tables")).toBeVisible();
@@ -1216,18 +1363,42 @@ test("observes an active run from another browser and reopens a terminal run fro
renderPage();
const observe = await screen.findByRole("button", {
name: "Observe active description generation",
name: "Run descriptions generation history",
});
expect(observe).toHaveTextContent("Run descriptions generation history");
expect(requestedLimit).toBe("50");
await user.click(observe);
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
expect(within(drawer).getByRole("heading", { name: "Running" })).toBeVisible();
const history = within(drawer).getByRole("region", { name: "Description generation history" });
await user.click(within(history).getByRole("button", { name: /Completed.*missing/i }));
expect(await within(drawer).findByRole("heading", { name: "Completed" })).toBeVisible();
});
test("keeps the sensitive suggestion history label stable while showing active status separately", async () => {
const runningRun = makeSensitiveDataSuggestionRun({
status: "running",
finishedAt: null,
updatedAt: new Date().toISOString(),
});
server.use(http.get(
"/api/catalog/sensitive-data-suggestion-runs",
() => HttpResponse.json([runningRun]),
));
renderPage();
const historyButton = await screen.findByRole("button", {
name: "Run sensitive suggestions history",
});
expect(historyButton).toHaveTextContent("Run sensitive suggestions history");
await waitFor(() => expect(historyButton).toHaveAttribute(
"title",
"Sensitive suggestion generation is active",
));
expect(historyButton.querySelector("[aria-hidden='true'].bg-primary")).not.toBeNull();
});
test.each([
"completed",
"completed_with_errors",
@@ -1256,7 +1427,7 @@ test.each([
client.setQueryData(unrelatedColumnKey, []);
await user.click(await screen.findByRole("button", {
name: "View description generation history",
name: "Run descriptions generation history",
}));
await waitFor(() => expect(client.getQueryState(tableKey)?.isInvalidated).toBe(true));
@@ -1299,7 +1470,7 @@ test("keeps selected tables when description generation cannot start", async ()
expect(await screen.findByText("A description generation run is already active.")).toBeVisible();
expect(screen.getByText("2 selected")).toBeVisible();
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
expect(document.body).not.toHaveTextContent("private provider detail");
});
@@ -1565,7 +1736,7 @@ test("starts one selected column with the configured default model", async () =>
scope: "selected_columns",
targetIds: [patientIdColumn.id],
}));
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
expect(drawer).toBeVisible();
expect(within(drawer).getByRole("heading", { name: "Queued" })).toBeVisible();
expect(await screen.findByText("Description generation started for 1 column")).toBeVisible();
@@ -1606,14 +1777,23 @@ test("shows database sensitive suggestions only for a selection and rejects mult
test("requests database-level sensitive suggestions for the only selected database", async () => {
const user = userEvent.setup();
let suggestionBody: unknown;
let suggestionFinished = false;
let historyCalls = 0;
const run = makeSensitiveDataSuggestionRun({ scope: "all", total: 1 });
server.use(
http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
models: [{ id: "local-qwen", label: "Local Qwen" }],
default: "local-qwen",
})),
http.get("/api/catalog/sensitive-data-suggestion-runs", () => {
historyCalls += 1;
return HttpResponse.json(suggestionFinished ? [run] : []);
}),
http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", async ({ request }) => {
suggestionBody = await request.json();
suggestionFinished = true;
return HttpResponse.json({
run,
suggestions: [{
columnId: patientIdColumn.id,
tableId: patientsTable.id,
@@ -1633,7 +1813,37 @@ test("requests database-level sensitive suggestions for the only selected databa
await user.click(screen.getByRole("button", { name: "Suggest sensitive fields" }));
await waitFor(() => expect(suggestionBody).toEqual({ modelId: "local-qwen", scope: "all" }));
expect(await screen.findByRole("complementary", { name: "Sensitive field review" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible();
await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2));
});
test("refetches sensitive suggestion history after a failed request", async () => {
const user = userEvent.setup();
let historyCalls = 0;
server.use(
http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({
models: [{ id: "local-qwen", label: "Local Qwen" }],
default: "local-qwen",
})),
http.get("/api/catalog/sensitive-data-suggestion-runs", () => {
historyCalls += 1;
return HttpResponse.json([]);
}),
http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", () => (
HttpResponse.json({
code: "sensitive_data_suggestion_invalid_response",
message: "The LLM returned an incomplete or invalid classification. No suggestions were applied.",
}, { status: 502 })
)),
);
renderPage({ rows: [makeDatabase()] });
const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ });
await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i }));
await user.click(screen.getByRole("button", { name: "Suggest sensitive fields" }));
await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2));
expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument();
});
test("requests sensitive suggestions only for selected tables", async () => {
@@ -1687,7 +1897,7 @@ test("requests sensitive suggestions only for selected tables", async () => {
scope: "selected_tables",
targetIds: [visitsTable.id],
}));
expect(await screen.findByRole("complementary", { name: "Sensitive field review" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible();
});
test("reviews AI-sensitive-field suggestions as an editable draft and saves only changed columns", async () => {
@@ -1798,7 +2008,7 @@ test("reviews AI-sensitive-field suggestions as an editable draft and saves only
scope: "selected_columns",
targetIds: [idColumn.id, nameColumn.id],
}));
const review = await screen.findByRole("complementary", { name: "Sensitive field review" });
const review = await screen.findByRole("dialog", { name: "Sensitive field review" });
expect(within(review).getByRole("checkbox", { name: "Protect patients.id" })).toBeChecked();
expect(within(review).getByRole("checkbox", { name: "Protect patients.name" })).toBeChecked();
expect(patches).toHaveLength(0);
@@ -1814,7 +2024,7 @@ test("reviews AI-sensitive-field suggestions as an editable draft and saves only
},
}]));
expect(await screen.findByText("Saved 1 sensitive flag")).toBeVisible();
await waitFor(() => expect(screen.queryByRole("complementary", { name: "Sensitive field review" })).not.toBeInTheDocument());
await waitFor(() => expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument());
await waitFor(() => expect(screen.getByRole("checkbox", { name: "Sensitive data for id" })).toBeChecked());
expect(screen.getByRole("checkbox", { name: "Sensitive data for name" })).not.toBeChecked();
});
@@ -1875,7 +2085,7 @@ test("polls a description run and renders its events in sequence order", async (
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
expect(within(drawer).getByText(/local-qwen/)).toBeVisible();
expect(within(drawer).getByText(/selected columns/)).toBeVisible();
expect(await within(drawer).findByRole("heading", { name: "Completed" }, { timeout: 4_000 })).toBeVisible();
@@ -1992,7 +2202,7 @@ test("keeps the selected column and shows a safe message when generation cannot
expect(await screen.findByText("A description generation run is already active.")).toBeVisible();
expect(screen.getByText("1 selected")).toBeVisible();
expect(screen.queryByRole("complementary", { name: "Description generation" })).not.toBeInTheDocument();
expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument();
expect(document.body).not.toHaveTextContent(/private-provider|sk-private-key/);
});
@@ -2057,7 +2267,7 @@ test("shows a basic failed run without exposing private model or provider fields
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
const drawer = await screen.findByRole("complementary", { name: "Description generation" });
const drawer = await screen.findByRole("dialog", { name: "Description generation" });
expect(await within(drawer).findByRole("heading", { name: "Failed" })).toBeVisible();
expect(within(drawer).getByText(/Approved model/)).toBeVisible();
expect(within(drawer).getByText("The model request failed before a description was saved.")).toBeVisible();
@@ -2200,7 +2410,7 @@ test("disables another generation start while the selected-column run is active"
await user.click(within(columnRow!).getByRole("checkbox", { name: /toggle row selection/i }));
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Generate description" }));
expect(await screen.findByRole("complementary", { name: "Description generation" })).toBeVisible();
expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible();
await user.click(screen.getByRole("button", { name: "Actions" }));
expect(await screen.findByRole("menuitem", { name: "Generate description" }))
@@ -2495,10 +2705,16 @@ test("opens the durable job drawer and confirms its exact destructive plan", asy
await user.click(screen.getByRole("tab", { name: "Tables" }));
await user.click(await screen.findByRole("button", { name: "Sync tables" }));
const synchronizationDrawer = await screen.findByRole("complementary", {
const synchronizationDrawer = await screen.findByRole("dialog", {
name: "Schema synchronization",
});
expect(synchronizationDrawer).toHaveClass("inset-y-2", "sm:inset-y-4");
expect(synchronizationDrawer).toHaveAttribute("data-catalog-panel");
expect(synchronizationDrawer.querySelector(
':scope > [data-catalog-panel-region="header"]',
)).toBeInTheDocument();
expect(synchronizationDrawer.querySelector(
':scope > [data-catalog-panel-region="body"]',
)).toBeInTheDocument();
expect(within(synchronizationDrawer).getByRole("listitem", {
name: "Confirmation required, in progress",
})).toHaveAttribute("aria-current", "step");