import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { setLocale } from "../i18n"; import userEvent from "@testing-library/user-event"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { http, HttpResponse } from "msw"; import { server } from "../test/msw"; import type { CatalogColumn, CatalogDatabase, CatalogRelationship, CatalogSyncRun, CatalogTable, DescriptionGenerationRun, SensitivityAnalysisRun, } from "../api/catalog-databases"; import { Toaster } from "../components/ui/sonner"; import { DatabaseManagementPage } from "./DatabaseManagementPage"; import { workspacePreferences } from "../workspaces/preferences"; import { ComposerFooter } from "./SteerInput"; import { useSessionStore } from "../store/sessionStore"; beforeEach(() => { localStorage.clear(); }); const noSecrets = { password: false, apiKey: false, sshPrivateKey: false, sshPrivateKeyPassphrase: false, sshKnownHosts: false, tlsCa: false, }; function makeDatabase(overrides: Partial = {}): CatalogDatabase { return { id: "11111111-1111-4111-8111-111111111111", workspaceId: "psd-clinical", workspaceName: "Policlinico San Donato", workspaceAvailable: true, workspaceRevision: { commit: "a".repeat(40), blob: "b".repeat(40) }, workspaceEvidence: { sourceType: "filesystem", state: "materialized_current_revision" }, runtimeBinding: { transport: "postgres_direct", configurationState: "ready", sessionTransportSupported: true, }, configured: true, engine: "postgres", databaseName: "warehouse", schema: "datawarehouse", version: 3, createdAt: "2026-08-27T08:00:00Z", updatedAt: "2026-08-27T09:00:00Z", binding: { transport: "postgres_direct", host: "db.internal", port: 5432, username: "reader", }, connectionStatus: "untested", secrets: { ...noSecrets }, ...overrides, }; } const unconfigured = makeDatabase({ id: undefined, workspaceId: "lab", workspaceName: "Research laboratory", configured: false, databaseName: "lab_warehouse", schema: "analytics", version: 0, createdAt: "", updatedAt: "", binding: { transport: "postgres_direct", port: 5432 }, }); const orphan = makeDatabase({ id: "22222222-2222-4222-8222-222222222222", workspaceId: "retired", workspaceName: "Retired workspace", workspaceAvailable: false, workspaceRevision: null, workspaceEvidence: { sourceType: null, state: "workspace_unavailable" }, runtimeBinding: null, }); function renderPage({ rows = [makeDatabase(), unconfigured, orphan], 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, ))); const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); const view = render( , ); return { ...view, client }; } test("updates database row actions and the open editor when switching EN and IT", async () => { renderPage({ presentation: "fleet" }); await userEvent.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); const name = await screen.findByLabelText("Database name"); const savedName = (name as HTMLInputElement).value; try { act(() => setLocale("it")); expect(screen.getByLabelText("Nome database")).toHaveValue(savedName); expect(screen.getByRole("button", { name: "Salva modifiche" })).toBeVisible(); expect(screen.getByRole("button", { name: "Verifica connessione" })).toBeVisible(); act(() => setLocale("en")); expect(screen.getByLabelText("Database name")).toHaveValue(savedName); expect(screen.getByRole("button", { name: "Save changes" })).toBeVisible(); } finally { act(() => setLocale("en")); } }); test("fleet entry opens the database list without workspace preparation or an automatic editor", async () => { renderPage({ presentation: "fleet" }); expect(await screen.findByRole("button", { name: "View tables for Policlinico San Donato" })).toBeVisible(); expect(screen.getByText("Research laboratory")).toBeVisible(); expect(screen.queryByRole("button", { name: "Back to list" })).not.toBeInTheDocument(); expect(screen.queryByText(/Workspace preparation/)).not.toBeInTheDocument(); expect(screen.queryByRole("link", { name: "Open workspace preprocessing" })).not.toBeInTheDocument(); }); test.each(["tables", "relationships"])("fresh entry ignores a previously persisted %s screen", async kind => { vi.stubEnv("MODE", "production"); localStorage.setItem("thothii:database-management-screen", JSON.stringify({ kind, workspaceId: "psd-clinical" })); try { renderPage({ presentation: "fleet" }); expect(await screen.findByRole("button", { name: "View tables for Policlinico San Donato" })).toBeVisible(); expect(screen.queryByRole("button", { name: "Back to databases" })).not.toBeInTheDocument(); } finally { vi.unstubAllEnvs(); } }); function makeSyncRun( scope: CatalogSyncRun["scope"], tableIds: string[] = [], ): CatalogSyncRun { return { id: `sync-${scope}`, databaseId: "11111111-1111-4111-8111-111111111111", scope, tableIds, state: "queued", phase: "queued", requestedDatabaseVersion: 3, plannedDiff: null, confirmationToken: null, counts: {}, errorCode: null, errorMessage: null, cancelRequested: false, createdAt: "2026-08-27T10:00:00Z", startedAt: null, updatedAt: "2026-08-27T10:00:00Z", finishedAt: null, heartbeatAt: null, }; } function makeDescriptionGenerationRun( overrides: Partial = {}, ): DescriptionGenerationRun { return { id: "77777777-7777-4777-8777-777777777777", databaseId: "11111111-1111-4111-8111-111111111111", modelId: "local/qwen", scope: "selected_columns", language: "it", status: "queued", total: 1, processed: 0, generated: 0, nonGeneratable: 0, failed: 0, createdAt: "2026-08-28T10:00:00Z", startedAt: null, updatedAt: "2026-08-28T10:00:00Z", finishedAt: null, errorSummary: null, ...overrides, }; } function makeSensitivityAnalysisRun( overrides: Partial = {}, ): SensitivityAnalysisRun { return { id: "99999999-9999-4999-8999-999999999999", databaseId: "11111111-1111-4111-8111-111111111111", engine: "local", modelId: null, policyVersion: "sensitivity-v2", scope: "selected_columns", status: "completed", total: 2, suggestedSensitive: 1, suggestedNonSensitive: 1, unknown: 0, 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, state: "succeeded", phase: "completed", startedAt: "2026-08-27T10:00:00Z", finishedAt: "2026-08-27T10:00:01Z", }; server.use( http.get("/api/catalog/sync-runs/:runId", () => HttpResponse.json(completed)), http.get("/api/catalog/sync-runs/:runId/events-list", () => HttpResponse.json([])), http.get("/api/catalog/databases/:databaseId/sync-runs", () => HttpResponse.json([completed])), ); } const synchronizationScopes = [ { scope: "tables", label: "Synchronize tables" }, { scope: "relationships", label: "Synchronize relationships" }, { scope: "all", label: "Synchronize all" }, ] as const; test("renders Fleet Ledger with real metrics, conceptual row tooltips, and persistent 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(screen.getByRole("region", { name: "Catalog status" })).toBeVisible(); expect(screen.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(); expect(screen.queryByRole("button", { name: /Add database/i })).not.toBeInTheDocument(); expect(screen.getByRole("columnheader", { name: /Catalog status/ })).toBeVisible(); const summary = screen.getByRole("region", { name: "Catalog summary" }); expect(await within(summary).findByText("2,275")).toBeVisible(); expect(within(summary).getByText("75%")).toBeVisible(); const modelSelector = await screen.findByRole("combobox", { name: "Global LLM model" }); expect(modelSelector).toHaveValue("local/qwen"); 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("region", { name: "Edit database form" }); expect(manager).toContainElement(editDrawer); expect(screen.queryByRole("dialog", { name: "Edit database" })).not.toBeInTheDocument(); await user.click(within(editDrawer).getByRole("button", { name: "Back to list" })); 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(); expect(within(actionPicker).queryByRole("option", { name: "Synchronize all columns" })).not.toBeInTheDocument(); await user.selectOptions(actionPicker, "generate-missing"); expect(screen.getAllByRole("combobox", { name: "Global LLM model" })).toHaveLength(1); expect(modelSelector).toHaveValue("local/qwen"); }); test("shows synchronization, description, and sensitive-data states independently", async () => { const configured = makeDatabase({ connectionStatus: "reachable", testedVersion: 3 }); const needsRuntimeConfiguration = makeDatabase({ ...unconfigured, workspaceEvidence: { sourceType: "http", state: "configuration_required" }, runtimeBinding: { transport: "rest_api", configurationState: "configuration_required", sessionTransportSupported: true, }, }); renderPage({ rows: [configured, needsRuntimeConfiguration, orphan], presentation: "fleet" }); const configuredRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); expect(within(configuredRow).getByText("Not synchronized")).toBeVisible(); expect(within(configuredRow).getByText(/Descriptions ·/)).toBeVisible(); expect(within(configuredRow).getByText(/Sensitive ·/)).toBeVisible(); const needsConfigurationRow = screen.getByRole("row", { name: /Research laboratory/ }); expect(within(needsConfigurationRow).getByText("Not synchronized")).toBeVisible(); expect(within(needsConfigurationRow).getByText(/Descriptions ·/)).toBeVisible(); const orphanRow = screen.getByRole("row", { name: /Retired workspace/ }); expect(within(orphanRow).getByText("Not synchronized")).toBeVisible(); }); test("shows the configured access type and endpoint in catalog status", async () => { const direct = makeDatabase(); const rest = makeDatabase({ id: "33333333-3333-4333-8333-333333333333", workspaceId: "rest-lab", workspaceName: "REST laboratory", binding: { transport: "rest_api", baseUrl: "https://203.0.113.18/dwh/", restPath: "/health", restAuth: "x-api-key", }, }); const ssh = makeDatabase({ id: "44444444-4444-4444-8444-444444444444", workspaceId: "ssh-lab", workspaceName: "SSH laboratory", binding: { transport: "ssh_tunnel", sshHost: "bastion.example.test", sshPort: 2222, sshUsername: "operator", sshTargetHost: "postgres.internal", sshTargetPort: 5432, username: "reader", }, }); renderPage({ rows: [direct, rest, ssh], presentation: "fleet" }); const directRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); expect(within(directRow).getByText("Direct PostgreSQL connection")).toBeVisible(); expect(within(directRow).getByText("db.internal:5432")).toBeVisible(); const restRow = screen.getByRole("row", { name: /REST laboratory/ }); expect(within(restRow).getByText("REST")).toBeVisible(); expect(within(restRow).getByText("203.0.113.18")).toBeVisible(); const sshRow = screen.getByRole("row", { name: /SSH laboratory/ }); expect(within(sshRow).getByText("SSH")).toBeVisible(); expect(within(sshRow).getByText("bastion.example.test:2222")).toBeVisible(); }); test("keeps Fleet Ledger visible when a catalog response omits the Evidence projection", async () => { const legacyDatabase = makeDatabase(); delete (legacyDatabase as Partial).workspaceEvidence; renderPage({ rows: [legacyDatabase], presentation: "fleet" }); expect(await screen.findByRole("heading", { name: "Database management" })).toBeVisible(); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); expect(within(databaseRow).getByText("Not synchronized")).toBeVisible(); }); test("opens the relationship map directly from a Fleet database and restores focus on return", async () => { const user = userEvent.setup(); server.use( http.get("/api/catalog/databases/:databaseId/relationships", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], presentation: "fleet", }); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); const relationshipsAction = within(databaseRow).getByRole("button", { name: "View relationships for Policlinico San Donato", }); expect(relationshipsAction.parentElement).toHaveAttribute("data-tooltip", "Relationships"); expect(relationshipsAction).not.toHaveAttribute("title"); await user.click(relationshipsAction); expect(await screen.findByRole("region", { name: "Relationships for Policlinico San Donato", })).toBeVisible(); expect(screen.getByText("Relationship map")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Back to databases" })); await waitFor(() => expect(screen.getByRole("button", { name: "View relationships for Policlinico San Donato", })).toHaveFocus()); }); test("refreshes the relationship KPI after building generated relationships", async () => { const user = userEvent.setup(); const generatedRelationship: CatalogRelationship = { id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", databaseId: "11111111-1111-4111-8111-111111111111", constraintName: null, sourceTableId: "ffffffff-ffff-4fff-8fff-ffffffffffff", sourceTableName: "visits", targetTableId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", targetTableName: "patients", updateRule: null, deleteRule: null, deferrable: false, initiallyDeferred: false, columns: [{ position: 1, sourceColumnId: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", sourceColumnName: "patient_id", targetColumnId: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", targetColumnName: "id", }], origin: "generated", status: "active", lastSyncedDatabaseVersion: null, lastSyncedAt: null, createdAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:00Z", }; let relationships: CatalogRelationship[] = []; server.use( http.get("/api/catalog/metrics", ({ request }) => HttpResponse.json({ scope: new URL(request.url).searchParams.has("databaseId") ? "database" : "global", databaseId: new URL(request.url).searchParams.get("databaseId"), tables: 2, columns: 3, sensitiveColumns: 0, relationships: relationships.length, descriptionTargets: 5, describedTargets: 0, descriptionCoverage: 0, updatedAt: "2026-08-27T10:00:00Z", })), http.get( "/api/catalog/databases/:databaseId/relationships", () => HttpResponse.json(relationships), ), http.post( "/api/catalog/databases/:databaseId/relationships/rebuild-generated", () => { relationships = [generatedRelationship]; return HttpResponse.json({ added: 1, alreadyPresent: 0, excluded: 0, ambiguous: 0 }); }, ), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], presentation: "fleet", }); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); await user.click(within(databaseRow).getByRole("button", { name: "View relationships for Policlinico San Donato", })); const summary = await screen.findByRole("region", { name: "Database summary" }); const relationshipMetric = within(summary).getByText("Relationships").nextElementSibling; expect(relationshipMetric).toHaveTextContent("0"); await user.selectOptions( screen.getByRole("combobox", { name: "Relationship action" }), "rebuild-generated", ); await user.click(screen.getByRole("button", { name: "Run action" })); expect(await screen.findByText("Build complete: 1 added, 0 already present, 0 excluded, 0 ambiguous.")) .toBeVisible(); await waitFor(() => expect(relationshipMetric).toHaveTextContent("1")); }); test("offers catalog configuration directly on an unconfigured Fleet workspace", async () => { const user = userEvent.setup(); renderPage({ rows: [unconfigured], presentation: "fleet" }); const databaseRow = await screen.findByRole("row", { name: /Research laboratory/ }); expect(within(databaseRow).queryByRole("button", { name: "View relationships for Research laboratory", })).not.toBeInTheDocument(); expect(within(databaseRow).queryByRole("button", { name: "View tables for Research laboratory", })).not.toBeInTheDocument(); await user.click(within(databaseRow).getByRole("button", { name: "Configure catalog for Research laboratory", })); const drawer = await screen.findByRole("region", { name: "Configure catalog form" }); expect(within(drawer).getByLabelText("Workspace")).toHaveAttribute("readonly"); expect(within(drawer).getByLabelText("Workspace")).toHaveValue("Research laboratory"); }); test("keeps the table back action in the Fleet Ledger breadcrumb while browsing 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(); expect(screen.getByRole("combobox", { name: "Table action" })).toBeDisabled(); expect(screen.queryByRole("button", { name: "Sync history" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Description history" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Sensitive history" })).not.toBeInTheDocument(); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const tableBackAction = await screen.findByRole("button", { name: "Back to tables" }); expect(tableBackAction).toBeVisible(); expect(screen.getByRole("combobox", { name: "Column action" })).toBeDisabled(); expect(screen.getByRole("navigation", { name: "Database hierarchy" }).parentElement) .toContainElement(tableBackAction); 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("uses the database back-action background treatment for the table back action", 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", })); const databaseBackAction = await screen.findByRole("button", { name: "Back to databases" }); expect(databaseBackAction).toHaveClass("thot-fleet-back-action"); const backgroundTreatment = (element: HTMLElement) => element.className .split(/\s+/) .filter((className) => /(^|:)bg-/.test(className)) .sort(); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const tableBackAction = await screen.findByRole("button", { name: "Back to tables" }); expect(tableBackAction).toHaveClass("thot-fleet-back-action"); expect(backgroundTreatment(tableBackAction)).toEqual(backgroundTreatment(databaseBackAction)); }); 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 Policlinico San Donato", })); await user.click(screen.getByRole("button", { name: "View schema synchronization logs" })); 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: [ { id: "openai/mini", label: "OpenAI Mini" }, { id: "local/qwen", label: "Local Qwen" }, ], default: "local/qwen", }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); await waitFor(() => expect(selector).toHaveValue("local/qwen")); }); test("Core and Admin share one canonical selection, including models with the same short ID", async () => { const models = [{ id: "local/qwen", label: "Local Qwen" }, { id: "remote/qwen", label: "Remote Qwen" }]; server.use( http.get("/api/catalog/databases", () => HttpResponse.json([makeDatabase()])), http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models, default: "local/qwen" })), http.get("/api/models", () => HttpResponse.json({ models: models.map((m) => ({ provider: m.id.split("/")[0], id: "qwen", name: m.label, reasoning: false, })) })), http.get("/api/settings", () => HttpResponse.json({ provider: "local", model: "qwen" })), ); const client = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render(); const admin = await screen.findByRole("combobox", { name: "Global LLM model" }); const core = await screen.findByRole("combobox", { name: "Model" }); await waitFor(() => expect(admin).toHaveValue("local/qwen")); act(() => useSessionStore.getState().setAgentActive(true)); expect(core).toBeDisabled(); expect(admin).toBeDisabled(); act(() => useSessionStore.getState().setAgentActive(false)); act(() => client.setQueryData(["description-generation-runs", 50], [makeDescriptionGenerationRun()])); await waitFor(() => expect(core).toBeDisabled()); await waitFor(() => expect(admin).toBeDisabled()); act(() => client.setQueryData(["description-generation-runs", 50], [])); await waitFor(() => expect(core).toBeEnabled()); await waitFor(() => expect(admin).toBeEnabled()); await userEvent.selectOptions(admin, "remote/qwen"); await waitFor(() => expect(core).toHaveValue("remote/qwen")); await userEvent.selectOptions(core, "local/qwen"); await waitFor(() => expect(admin).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: "Global LLM 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: "View description generation history", }); const suggestionHistoryButton = within(metadataControls).getByRole("button", { name: "View sensitivity analysis 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("View description generation history"); expect(descriptionHistoryButton).toHaveClass("disabled:opacity-70"); expect(suggestionHistoryButton).toBeVisible(); expect(suggestionHistoryButton).toBeEnabled(); expect(suggestionHistoryButton).toHaveTextContent("View sensitivity analysis history"); expect(toolbar.lastElementChild).toBe(actions); expect(actions).toHaveClass("sm:justify-end"); expect(actions).toContainElement(screen.getByRole("button", { name: "Refresh" })); expect(within(actions).queryByRole("button", { name: /Add database/i })).not.toBeInTheDocument(); 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: "Sensitivity analysis history" }); expect(within(suggestionDrawer).getByText("No sensitivity analysis runs yet.")).toBeVisible(); }); test("changes the global model shared with Core", async () => { const user = userEvent.setup(); server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [ { id: "openai/mini", label: "OpenAI Mini" }, { id: "local/qwen", label: "Local Qwen" }, ], default: "local/qwen", }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); await waitFor(() => expect(selector).toHaveValue("local/qwen")); await user.selectOptions(selector, "openai/mini"); expect(selector).toHaveValue("openai/mini"); expect(workspacePreferences.load()).toMatchObject({ provider: "openai", model: "mini" }); }); test("explains application setup when no metadata-description model is configured", async () => { server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [], default: null, }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); expect(selector).toBeDisabled(); expect(await screen.findByText( "No metadata-generation LLM model is configured for this installation.", )).toBeVisible(); }); test("uses a safe disabled setup state when metadata-description models are unavailable", async () => { server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ code: "catalog_unavailable", message: "internal model registry details", }, { status: 503 }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); await waitFor(() => expect(selector).toHaveDisplayValue("Model choices unavailable")); expect(selector).toBeDisabled(); expect(screen.getByText( "Global LLM model choices are unavailable.", )).toBeVisible(); expect(screen.queryByText("internal model registry details")).not.toBeInTheDocument(); }); test.each([null, "missing-model"])( "disables metadata-description selection when the public response has no usable default (%s)", async (configuredDefault) => { server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "openai/mini", label: "OpenAI Mini" }], default: configuredDefault, }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); await waitFor(() => expect(selector).toHaveDisplayValue("No usable model configured")); expect(selector).toBeDisabled(); expect(screen.getByText( "No metadata-generation LLM model is configured for this installation.", )).toBeVisible(); }, ); test("renders only the public metadata-generation model contract", async () => { server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "approved/model", label: "Approved model", provider: "private-provider", model: "private/upstream-model", endpoint: "https://private-model.example/v1", apiKeyEnv: "PRIVATE_MODEL_API_KEY", credential: "raw-private-credential", }], default: "approved/model", }))); renderPage(); const selector = await screen.findByRole("combobox", { name: "Global LLM model" }); await waitFor(() => expect(selector).toHaveValue("approved/model")); expect(within(selector).getAllByRole("option").map((option) => option.textContent)) .toEqual(["Approved model"]); expect(document.body).not.toHaveTextContent(/private-provider|private\/upstream-model/); expect(document.body).not.toHaveTextContent(/private-model\.example|PRIVATE_MODEL_API_KEY/); expect(document.body).not.toHaveTextContent("raw-private-credential"); }); test("starts with a full-width list and applies the row action matrix", async () => { renderPage(); expect(await screen.findByRole("button", { name: "View Policlinico San Donato" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Edit Policlinico San Donato" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Delete Policlinico San Donato" })).toBeEnabled(); expect(screen.getByRole("button", { name: "View Research laboratory" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Configure catalog for Research laboratory" })).toBeEnabled(); expect(screen.queryByRole("button", { name: "Edit Research laboratory" })).not.toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Delete Research laboratory" })).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "View Retired workspace" })).toBeEnabled(); expect(screen.getByRole("button", { name: "Edit Retired workspace" })).toBeDisabled(); expect(screen.getByRole("button", { name: "Delete Retired workspace" })).toBeEnabled(); expect(screen.queryByRole("heading", { name: "Database details" })).not.toBeInTheDocument(); }); test.each(synchronizationScopes)( "selected database Actions offers and starts $scope synchronization", async ({ scope, label }) => { const user = userEvent.setup(); let startBody: unknown; const run = makeSyncRun(scope); registerCompletedSyncRun(run); server.use(http.post("/api/catalog/databases/:databaseId/sync-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(run, { status: 202 }); })); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); 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: "Actions" })); expect(await screen.findByRole("menuitem", { name: synchronizationScopes[0].label })).toBeEnabled(); for (const option of synchronizationScopes.slice(1)) { expect(screen.getByRole("menuitem", { name: option.label })).toBeEnabled(); } await user.click(screen.getByRole("menuitem", { name: label })); await waitFor(() => expect(startBody).toEqual({ version: 3, scope, tableIds: [] })); expect(screen.getByText("1 selected")).toBeVisible(); }, ); test("discloses source sampling before starting Generate missing descriptions", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun({ scope: "missing", total: 3 }); let startBody: unknown; server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate missing descriptions" })); expect(screen.getByText("Generate missing descriptions for Policlinico San Donato?")).toBeVisible(); expect(screen.getByRole("note", { name: "Metadata generation source data disclosure", })).toHaveTextContent( "When available, up to five real source rows and five representative values from columns not marked sensitive are sent to the selected provider.", ); expect(startBody).toBeUndefined(); await user.click(screen.getByRole("button", { name: "Generate missing descriptions" })); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "missing", })); expect(screen.queryByText(/replace existing generated descriptions/i)).not.toBeInTheDocument(); expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible(); }); test("keeps Fleet Generate missing descriptions behind the source-data disclosure", async () => { const user = userEvent.setup(); let generationStarts = 0; server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => { generationStarts += 1; return HttpResponse.json(makeDescriptionGenerationRun({ scope: "missing" }), { status: 202 }); }), ); renderPage({ rows: [makeDatabase()], presentation: "fleet" }); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.selectOptions( screen.getByRole("combobox", { name: "Batch action" }), "generate-missing", ); await waitFor(() => expect(screen.getByRole("combobox", { name: "Global LLM model", })).toHaveValue("local/qwen")); await user.click(screen.getByRole("button", { name: "Run action" })); expect(screen.getByRole("note", { name: "Metadata generation source data disclosure", })).toBeVisible(); expect(generationStarts).toBe(0); await user.click(screen.getByRole("button", { name: "Cancel" })); expect(screen.queryByRole("note", { name: "Metadata generation source data disclosure", })).not.toBeInTheDocument(); expect(generationStarts).toBe(0); }); test("copies generated descriptions to every database table and column after explicit confirmation", async () => { const user = userEvent.setup(); let consolidationBody: unknown; server.use( http.post("/api/catalog/databases/:databaseId/descriptions/consolidate", async ({ request }) => { consolidationBody = await request.json(); return HttpResponse.json({ copied: 7, skipped: 2 }); }), ); const database = makeDatabase(); const { client } = renderPage({ rows: [database], presentation: "fleet" }); const tablesQuery = ["catalog-tables", database.id] as const; const firstColumnsQuery = ["catalog-columns", database.id, "table-one"] as const; const secondColumnsQuery = ["catalog-columns", database.id, "table-two"] as const; const unrelatedColumnsQuery = ["catalog-columns", "other-database", "table-one"] as const; for (const queryKey of [ tablesQuery, firstColumnsQuery, secondColumnsQuery, unrelatedColumnsQuery, ]) client.setQueryData(queryKey, []); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.selectOptions( screen.getByRole("combobox", { name: "Batch action" }), "consolidate-descriptions", ); expect(screen.getByRole("option", { name: "Copy generated description to descriptions" })) .toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Run action" })); expect(screen.getByText( "Copy generated descriptions to Description fields in Policlinico San Donato?", )).toBeVisible(); expect(screen.getByText(/table and column description will replace its current Description value/i)) .toBeVisible(); expect(consolidationBody).toBeUndefined(); await user.click(screen.getByRole("button", { name: "Copy to descriptions" })); await waitFor(() => expect(consolidationBody).toEqual({ target: "database" })); expect(await screen.findByText("Copied 7 descriptions; skipped 2")).toBeVisible(); await waitFor(() => { expect(client.getQueryState(tablesQuery)?.isInvalidated).toBe(true); expect(client.getQueryState(firstColumnsQuery)?.isInvalidated).toBe(true); expect(client.getQueryState(secondColumnsQuery)?.isInvalidated).toBe(true); }); expect(client.getQueryState(unrelatedColumnsQuery)?.isInvalidated).toBe(false); expect(screen.getByText("1 selected")).toBeVisible(); }); test("confirms generating all descriptions, supports cancel, and sends the database-wide scope", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun({ scope: "all", total: 6 }); let startBody: unknown; server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate all descriptions" })); expect(screen.getByText("Replace generated descriptions for Policlinico San Donato?")).toBeVisible(); expect(screen.getByText(/existing generated descriptions for eligible tables and columns will be replaced/i)).toBeVisible(); expect(screen.getByRole("note", { name: "Metadata generation source data disclosure", })).toHaveTextContent(/real source rows.*selected provider/i); expect(startBody).toBeUndefined(); await user.click(screen.getByRole("button", { name: "Cancel" })); expect(screen.queryByText(/Replace generated descriptions for/)).not.toBeInTheDocument(); expect(startBody).toBeUndefined(); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate all descriptions" })); await user.click(screen.getByRole("button", { name: "Generate all descriptions" })); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "all", })); expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible(); }); test("keeps the database selected and safely explains when no descriptions are eligible", 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.post("/api/catalog/databases/:databaseId/description-generation-runs", () => HttpResponse.json({ code: "description_generation_no_eligible_targets", message: "private provider and catalog detail", }, { status: 409 })), ); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate missing descriptions" })); await user.click(screen.getByRole("button", { name: "Generate missing descriptions" })); expect(await screen.findByText( "No eligible catalog tables or columns need description generation.", )).toBeVisible(); expect(screen.getByText("1 selected")).toBeVisible(); expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument(); expect(document.body).not.toHaveTextContent("private provider and catalog detail"); }); test.each([ { scope: "missing" as const, status: "completed" as const }, { scope: "all" as const, status: "completed_with_errors" as const }, ])("refreshes every table and column result after a database-wide $scope run is $status", async ({ scope, status, }) => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun({ scope, total: 4 }); const terminalRun = makeDescriptionGenerationRun({ scope, status, total: 4, processed: 4, generated: 3, failed: status === "completed_with_errors" ? 1 : 0, startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:02Z", finishedAt: "2026-08-28T10:00:02Z", }); server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(terminalRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); const { client } = renderPage({ rows: [makeDatabase()] }); const databaseId = queuedRun.databaseId; const tablesQuery = ["catalog-tables", databaseId] as const; const firstColumnsQuery = ["catalog-columns", databaseId, "table-one"] as const; const secondColumnsQuery = ["catalog-columns", databaseId, "table-two"] as const; const unrelatedTablesQuery = ["catalog-tables", "other-database"] as const; const unrelatedColumnsQuery = ["catalog-columns", "other-database", "table-one"] as const; for (const queryKey of [ tablesQuery, firstColumnsQuery, secondColumnsQuery, unrelatedTablesQuery, unrelatedColumnsQuery, ]) client.setQueryData(queryKey, []); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: scope === "all" ? "Generate all descriptions" : "Generate missing descriptions", })); await user.click(screen.getByRole("button", { name: scope === "all" ? "Generate all descriptions" : "Generate missing descriptions", })); expect(await screen.findByRole("heading", { name: status === "completed" ? "Completed" : "Completed with errors", })).toBeVisible(); await waitFor(() => { expect(client.getQueryState(tablesQuery)?.isInvalidated).toBe(true); expect(client.getQueryState(firstColumnsQuery)?.isInvalidated).toBe(true); expect(client.getQueryState(secondColumnsQuery)?.isInvalidated).toBe(true); }); expect(client.getQueryState(unrelatedTablesQuery)?.isInvalidated).toBe(false); expect(client.getQueryState(unrelatedColumnsQuery)?.isInvalidated).toBe(false); }); test.each([ { reason: "database.manage is absent", canManage: false, rows: [makeDatabase()], models: [{ id: "local/qwen", label: "Local Qwen" }], configuredDefault: "local/qwen", selectedRows: 1, }, { reason: "no usable model is configured", canManage: true, rows: [makeDatabase()], models: [], configuredDefault: null, selectedRows: 1, }, { reason: "the Workspace Database is not configured", canManage: true, rows: [unconfigured], models: [{ id: "local/qwen", label: "Local Qwen" }], configuredDefault: "local/qwen", selectedRows: 1, }, { reason: "more than one Workspace Database is selected", canManage: true, rows: [ makeDatabase(), makeDatabase({ id: "44444444-4444-4444-8444-444444444444", workspaceId: "radiology", workspaceName: "Radiology", }), ], models: [{ id: "local/qwen", label: "Local Qwen" }], configuredDefault: "local/qwen", selectedRows: 2, }, ])("disables database-wide generation when $reason", async ({ canManage, rows, models, configuredDefault, selectedRows, }) => { const user = userEvent.setup(); server.use(http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models, default: configuredDefault, }))); renderPage({ rows, canManage }); for (const database of rows.slice(0, selectedRows)) { const row = await screen.findByRole("row", { name: new RegExp(database.workspaceName) }); await user.click(within(row).getByRole("checkbox", { name: /toggle row selection/i })); } await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate all descriptions" })) .toHaveAttribute("aria-disabled", "true"); expect(screen.getByRole("menuitem", { name: "Generate missing descriptions" })) .toHaveAttribute("aria-disabled", "true"); }); test("disables database-wide generation while a description generation is active", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun({ scope: "missing" }); server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json({ ...queuedRun, status: "running", startedAt: "2026-08-28T10:00:01Z", })), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase()] }); let 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate missing descriptions" })); await user.click(screen.getByRole("button", { name: "Generate missing descriptions" })); expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible(); await user.click(screen.getByRole("button", { name: "Close description generation" })); databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate all descriptions" })) .toHaveAttribute("aria-disabled", "true"); expect(screen.getByRole("menuitem", { name: "Generate missing descriptions" })) .toHaveAttribute("aria-disabled", "true"); }); test("selected database Actions confirms and deletes catalog tables for the full selection", async () => { const user = userEvent.setup(); let cleanupBody: unknown; const second = makeDatabase({ id: "44444444-4444-4444-8444-444444444444", workspaceId: "radiology", workspaceName: "Radiology", databaseName: "radiology", }); server.use(http.post("/api/catalog/databases/metadata-cleanup", async ({ request }) => { cleanupBody = await request.json(); return HttpResponse.json({ tables: 8, columns: 42, relationships: 5 }); })); renderPage({ rows: [makeDatabase(), second] }); const firstRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); const secondRow = await screen.findByRole("row", { name: /Radiology/ }); await user.click(within(firstRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(secondRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Delete all tables" })).toBeEnabled(); expect(screen.getByRole("menuitem", { name: "Delete all relationships" })).toBeEnabled(); await user.click(screen.getByRole("menuitem", { name: "Delete all tables" })); expect(screen.getByText("Delete all catalog tables for 2 databases?")).toBeVisible(); expect(screen.getByText(/database configurations and source databases are unchanged/i)).toBeVisible(); expect(cleanupBody).toBeUndefined(); await user.click(within(secondRow).getByRole("checkbox", { name: /toggle row selection/i })); expect(screen.queryByText(/Delete all catalog tables for/)).not.toBeInTheDocument(); expect(cleanupBody).toBeUndefined(); await user.click(within(secondRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Delete all tables" })); await waitFor(() => expect(screen.getByRole("button", { name: "Delete catalog tables" })).toHaveFocus()); await user.keyboard("{Escape}"); await waitFor(() => expect(screen.getByRole("button", { name: "Actions" })).toHaveFocus()); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Delete all tables" })); await user.click(screen.getByRole("button", { name: "Delete catalog tables" })); await waitFor(() => expect(cleanupBody).toEqual({ databaseIds: [ "11111111-1111-4111-8111-111111111111", "44444444-4444-4444-8444-444444444444", ], target: "tables", })); expect(screen.getByText("2 selected")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Clear" })); expect(screen.queryByText("2 selected")).not.toBeInTheDocument(); }); test("presents completed database synchronization steps as success and skips unneeded confirmation", async () => { const user = userEvent.setup(); const run = { ...makeSyncRun("all"), counts: { tables: 163, columns: 2_275, relationships: 18 }, }; registerCompletedSyncRun(run); server.use(http.post("/api/catalog/databases/:databaseId/sync-runs", () => ( HttpResponse.json(run, { status: 202 }) ))); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Synchronize all" })); expect(await screen.findByRole("heading", { name: "Succeeded" })).toBeVisible(); const steps = screen.getByRole("list", { name: "Synchronization steps" }); expect(steps).toHaveClass("grid-cols-2", "sm:grid-cols-3"); const connected = within(steps).getByRole("listitem", { name: "Connected, completed" }); expect(connected.querySelector("svg")).toHaveClass("text-[oklch(var(--success))]"); expect(within(steps).getByText("Tables read")).toBeVisible(); const confirmation = within(steps).getByRole("listitem", { name: "Confirmation not required, not required", }); expect(confirmation.querySelector("svg")).toHaveClass("text-muted-foreground"); const completed = within(steps).getByRole("listitem", { name: "Completed, completed" }); expect(completed.querySelector("svg")).toHaveClass("text-[oklch(var(--success))]"); expect(steps.querySelectorAll(".text-primary")).toHaveLength(0); }); test("database Overview exposes the database synchronization scopes", async () => { const user = userEvent.setup(); let startBody: unknown; const run = makeSyncRun("relationships"); registerCompletedSyncRun(run); server.use(http.post("/api/catalog/databases/:databaseId/sync-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(run, { status: 202 }); })); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("button", { name: "Synchronize database schema" })); expect(await screen.findByRole("menuitem", { name: synchronizationScopes[0].label })).toBeEnabled(); for (const option of synchronizationScopes.slice(1)) { expect(screen.getByRole("menuitem", { name: option.label })).toBeEnabled(); } await user.click(screen.getByRole("menuitem", { name: "Synchronize relationships" })); await waitFor(() => expect(startBody).toEqual({ version: 3, scope: "relationships", tableIds: [], })); }); test("replaces the list with View and returns focus to the originating action", async () => { const user = userEvent.setup(); renderPage({ rows: [makeDatabase()] }); const view = await screen.findByRole("button", { name: "View Policlinico San Donato" }); await user.click(view); expect(screen.getByRole("heading", { name: "Database details" })).toBeVisible(); expect(screen.getByDisplayValue("db.internal")).toHaveAttribute("readonly"); expect(screen.getByRole("button", { name: "Test connection" })).toBeEnabled(); expect(screen.queryByRole("button", { name: "Save changes" })).not.toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Back to list" })); await waitFor(() => expect(view).toHaveFocus()); expect(screen.getByRole("button", { name: "View Policlinico San Donato" })).toBeVisible(); }); test("configures the catalog for the selected workspace and locks that workspace", async () => { const user = userEvent.setup(); let postBody: unknown; const saved = makeDatabase({ ...unconfigured, id: "33333333-3333-4333-8333-333333333333", configured: true, version: 1, binding: { transport: "rest_api", baseUrl: "https://psd.example/api", restPath: "/health", restAuth: "x-api-key" }, }); let rows = [unconfigured]; server.use( http.post("/api/catalog/databases", async ({ request }) => { postBody = await request.json(); rows = [saved]; return HttpResponse.json(saved, { status: 201 }); }), ); renderPage({ rows: () => rows }); await user.click(await screen.findByRole("button", { name: "Configure catalog for Research laboratory" })); expect(screen.getByRole("heading", { name: "Configure catalog" })).toBeVisible(); expect(screen.getByLabelText("Workspace")).toHaveAttribute("readonly"); await user.selectOptions(screen.getByLabelText("Transport"), "rest_api"); await user.type(screen.getByLabelText("Base URL"), "https://psd.example/api"); expect(screen.getByLabelText("Diagnostic endpoint")).toHaveValue("/health"); expect(screen.getByLabelText("Diagnostic endpoint")).toHaveAttribute("readonly"); await user.click(screen.getByRole("button", { name: "Save catalog configuration" })); await waitFor(() => expect(postBody).toMatchObject({ workspaceId: "lab", binding: expect.objectContaining({ transport: "rest_api", baseUrl: "https://psd.example/api" }), })); expect(await screen.findByRole("heading", { name: "Edit database" })).toBeVisible(); }); test("has no global database creation path and scopes configuration to the chosen row", async () => { const user = userEvent.setup(); const second = makeDatabase({ ...unconfigured, workspaceId: "radiology", workspaceName: "Radiology", databaseName: "radiology_dwh", }); renderPage({ rows: [makeDatabase(), unconfigured, second] }); expect(screen.queryByRole("button", { name: /Add database/i })).not.toBeInTheDocument(); await user.click(await screen.findByRole("button", { name: "Configure catalog for Radiology" })); expect(screen.getByRole("heading", { name: "Configure catalog" })).toBeVisible(); const workspace = screen.getByLabelText("Workspace"); expect(workspace).toHaveAttribute("readonly"); expect(workspace).toHaveValue("Radiology"); expect(screen.getByDisplayValue("radiology_dwh")).toBeVisible(); }); test("guards a dirty Edit, disables testing, and reports navigation state", async () => { const user = userEvent.setup(); const navigation = vi.fn(); const confirm = vi.spyOn(window, "confirm").mockReturnValue(false); renderPage({ rows: [makeDatabase()], onNavigationStateChange: navigation }); await user.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); const schema = screen.getByLabelText("Schema"); await user.clear(schema); await user.type(schema, "reporting"); expect(screen.getByRole("button", { name: "Test connection" })).toBeDisabled(); await waitFor(() => expect(navigation).toHaveBeenLastCalledWith({ dirty: true, busy: false })); await user.click(screen.getByRole("button", { name: "Back to list" })); expect(confirm).toHaveBeenCalledWith("Discard unsaved database changes?"); expect(screen.getByRole("heading", { name: "Edit database" })).toBeVisible(); confirm.mockReturnValue(true); await user.click(screen.getByRole("button", { name: "Back to list" })); expect(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })).toBeVisible(); }); test("uses an in-page destructive form and returns the YAML workspace to Not configured", async () => { const user = userEvent.setup(); let row = makeDatabase(); let deleteCalled = false; let deleteVersion: string | null = null; server.use( http.delete("/api/catalog/databases/:id", ({ request }) => { deleteCalled = true; deleteVersion = new URL(request.url).searchParams.get("version"); row = { ...row, id: undefined, configured: false, version: 0, updatedAt: "", connectionStatus: "untested", secrets: { ...noSecrets }, }; return new HttpResponse(null, { status: 204 }); }), ); renderPage({ rows: () => [row] }); await user.click(await screen.findByRole("button", { name: "Delete Policlinico San Donato" })); expect(screen.getByRole("heading", { name: "Delete database" })).toBeVisible(); expect(screen.getByText("This removes the local database configuration.")).toBeVisible(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Delete database" })); await waitFor(() => expect(deleteCalled).toBe(true)); expect(deleteVersion).toBe("3"); expect(await screen.findByRole("button", { name: "Configure catalog for Policlinico San Donato" })).toBeEnabled(); expect(screen.queryByRole("button", { name: "Delete Policlinico San Donato" })).not.toBeInTheDocument(); }); test("tests only the persisted version and updates the visible connection status", async () => { const user = userEvent.setup(); let testBody: unknown; let row = makeDatabase(); server.use(http.post("/api/catalog/databases/:id/test", async ({ request }) => { testBody = await request.json(); row = makeDatabase({ version: 4, connectionStatus: "reachable", testedVersion: 4 }); const testResponse: Partial = { ...row }; delete testResponse.workspaceName; return HttpResponse.json(testResponse); })); renderPage({ rows: () => [row] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("button", { name: "Test connection" })); await waitFor(() => expect(testBody).toEqual({ version: 3 })); const resultDialog = await screen.findByRole("dialog", { name: "Connection test successful" }); expect(resultDialog).toBeVisible(); expect(within(resultDialog).getByText("Policlinico San Donato is reachable.")).toBeVisible(); await user.click(within(resultDialog).getByRole("button", { name: "Done" })); expect(within(screen.getByRole("region", { name: "Database details form" })).getByText("Reachable")).toBeVisible(); }); test("reports an unsuccessful selected connection test as a failure", async () => { const user = userEvent.setup(); const failed = makeDatabase({ connectionStatus: "failed", testedVersion: 3, lastErrorMessage: "Password authentication failed", }); const testResponse: Partial = { ...failed }; delete testResponse.workspaceName; server.use(http.post("/api/catalog/databases/:id/test", () => HttpResponse.json(testResponse))); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Test connections" })); const resultDialog = await screen.findByRole("dialog", { name: "Connection test failed" }); expect(resultDialog).toBeVisible(); expect(within(resultDialog).getByText(/Policlinico San Donato: Password authentication failed/)).toBeVisible(); expect(screen.queryByText("1 connection tested")).not.toBeInTheDocument(); }); test("keeps Fleet synchronization available after an informative connection failure", async () => { const user = userEvent.setup(); renderPage({ rows: [makeDatabase({ connectionStatus: "failed", testedVersion: 3, lastErrorCode: "connector_unavailable", lastErrorMessage: "Password authentication failed", })], presentation: "fleet", }); const databaseRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); await user.click(within(databaseRow).getByRole("checkbox", { name: /toggle row selection/i })); const actionPicker = screen.getByRole("combobox", { name: "Batch action" }); await user.selectOptions(actionPicker, "sync-all"); expect(within(actionPicker).getByRole("option", { name: "Synchronize all" })).toBeVisible(); expect(screen.getByRole("button", { name: "Run action" })).toBeEnabled(); }); test("reports a successful selected connection test as a success", async () => { const user = userEvent.setup(); const reachable = makeDatabase({ connectionStatus: "reachable", testedVersion: 3, }); const testResponse: Partial = { ...reachable }; delete testResponse.workspaceName; server.use(http.post("/api/catalog/databases/:id/test", () => HttpResponse.json(testResponse))); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Test connections" })); const resultDialog = await screen.findByRole("dialog", { name: "Connection test successful" }); expect(resultDialog).toBeVisible(); expect(within(resultDialog).getByText("Policlinico San Donato is reachable.")).toBeVisible(); }); test("retains a stale draft and requires an explicit reload", async () => { const user = userEvent.setup(); server.use( http.patch("/api/catalog/databases/:id", () => HttpResponse.json({ code: "database_stale", message: "The database changed", }, { status: 409 })), ); renderPage({ rows: [makeDatabase()] }); await user.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); const schema = screen.getByLabelText("Schema"); await user.clear(schema); await user.type(schema, "draft_schema"); await user.click(screen.getByRole("button", { name: "Save changes" })); expect(await screen.findByText("A newer database configuration is available.")).toBeVisible(); expect(schema).toHaveValue("draft_schema"); expect(screen.getByRole("button", { name: "Save changes" })).toBeDisabled(); await user.click(screen.getByRole("button", { name: "Keep editing" })); expect(screen.getByText("Reload the latest values before this configuration can be changed.")).toBeVisible(); expect(schema).toHaveValue("draft_schema"); }); test("marks an open form stale when a background refetch advances the catalog version", async () => { const user = userEvent.setup(); const { client } = renderPage({ rows: [makeDatabase()] }); await user.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); act(() => { client.setQueryData(["catalog-databases"], [makeDatabase({ version: 4, schema: "server_schema" })]); }); expect(await screen.findByText("A newer database configuration is available.")).toBeVisible(); expect(screen.getByLabelText("Schema")).toHaveValue("datawarehouse"); expect(screen.getByRole("button", { name: "Save changes" })).toBeDisabled(); }); test("does not submit credentials hidden by a transport or authentication change", async () => { const user = userEvent.setup(); let putCalled = false; let patchBody: Record | undefined; let row = makeDatabase(); server.use( http.patch("/api/catalog/databases/:id", async ({ request }) => { patchBody = await request.json() as Record; row = makeDatabase({ version: 4, binding: { transport: "rest_api", baseUrl: "https://psd.example/api", restPath: "/health", restAuth: "none" }, }); return HttpResponse.json(row); }), http.put("/api/catalog/databases/:id/secrets", () => { putCalled = true; return HttpResponse.json(row); }), ); renderPage({ rows: () => [row] }); await user.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); await user.type(screen.getByLabelText("Password"), "must-not-be-sent"); await user.selectOptions(screen.getByLabelText("Transport"), "rest_api"); await user.type(screen.getByLabelText("Base URL"), "https://psd.example/api"); await user.selectOptions(screen.getByLabelText("Authentication"), "none"); await user.click(screen.getByRole("button", { name: "Save changes" })); await waitFor(() => expect(patchBody).toBeDefined()); expect(putCalled).toBe(false); expect(patchBody).toMatchObject({ version: 3, binding: { transport: "rest_api", baseUrl: "https://psd.example/api", restPath: "/health", restAuth: "none", }, }); expect((patchBody?.binding as Record).host).toBeUndefined(); }); test("preserves typed secrets and offers a retry when the configuration save is only partial", async () => { const user = userEvent.setup(); let putCalls = 0; let row = makeDatabase(); server.use( http.put("/api/catalog/databases/:id/secrets", async ({ request }) => { putCalls += 1; const body = await request.json(); expect(body).toEqual({ version: 3, values: { password: "transient-secret" } }); if (putCalls === 1) { return HttpResponse.json({ code: "catalog_unavailable", message: "Secret store unavailable", }, { status: 503 }); } row = makeDatabase({ version: 4, secrets: { ...noSecrets, password: true } }); return HttpResponse.json(row); }), ); renderPage({ rows: () => [row] }); await user.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" })); await user.type(screen.getByLabelText("Password"), "transient-secret"); await user.click(screen.getByRole("button", { name: "Save changes" })); expect(await screen.findByText("Database configuration saved; secret update could not be confirmed.")).toBeVisible(); expect(screen.getByLabelText("Password")).toHaveValue("transient-secret"); expect(screen.getByRole("button", { name: "Retry secrets" })).toBeEnabled(); await user.clear(screen.getByLabelText("Schema")); await user.type(screen.getByLabelText("Schema"), "reporting_after_retry"); await user.click(screen.getByRole("button", { name: "Retry secrets" })); await waitFor(() => expect(putCalls).toBe(2)); expect(screen.queryByText("Database configuration saved; secret update could not be confirmed.")).not.toBeInTheDocument(); expect(screen.getByLabelText("Schema")).toHaveValue("reporting_after_retry"); expect(screen.getByRole("button", { name: "Save changes" })).toBeEnabled(); }); test("shows secret status without exposing or enabling values when permission is absent", async () => { const user = userEvent.setup(); renderPage({ rows: [makeDatabase({ secrets: { ...noSecrets, password: true } })], canManageSecrets: false, }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); expect(screen.getByLabelText("Password")).toHaveValue("Configured"); expect(screen.getByLabelText("Password")).toHaveAttribute("readonly"); await user.click(screen.getByRole("button", { name: "Back to list" })); await user.click(screen.getByRole("button", { name: "Edit Policlinico San Donato" })); expect(await screen.findByRole("heading", { name: "Edit database" })).toBeVisible(); expect(screen.getByLabelText("Password")).toBeDisabled(); expect(screen.getByText(/requires the workspace\.secrets\.manage permission/i)).toBeVisible(); }); const patientsTable: CatalogTable = { id: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", databaseId: "11111111-1111-4111-8111-111111111111", name: "patients", sourceComment: "Patients imported from the clinical source", description: null, generatedDescription: null, version: 1, createdAt: "2026-08-27T08:00:00Z", updatedAt: "2026-08-27T09:00:00Z", }; const patientIdColumn: CatalogColumn = { id: "dddddddd-dddd-4ddd-8ddd-dddddddddddd", tableId: patientsTable.id, name: "id", ordinalPosition: 1, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: 1, isPrimaryKey: true, isForeignKey: false, foreignKeyCount: 0, sourceComment: "Patient identifier", description: null, generatedDescription: null, sensitive: false, lastSyncedDatabaseVersion: 3, lastSyncedAt: "2026-08-27T10:00:00Z", version: 1, createdAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:00Z", }; test("keeps column synchronization available when the latest connection test failed", 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([patientIdColumn])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "failed", testedVersion: 3, lastErrorCode: "connector_unavailable", lastErrorMessage: "The database connector could not be reached or authenticated.", })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const rows = await screen.findAllByRole("row", { name: /Patient identifier/ }); const selectableRow = rows.find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); expect(selectableRow).toBeDefined(); await user.click(within(selectableRow!).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Synchronize columns for this table" })) .not.toHaveAttribute("aria-disabled", "true"); }); test("saves a manual sensitive flag change from the Fleet columns toolbar", async () => { const user = userEvent.setup(); const nameColumn: CatalogColumn = { ...patientIdColumn, id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", name: "name", ordinalPosition: 2, primaryKeyPosition: null, isPrimaryKey: false, sourceComment: "Patient name", }; const columns = [patientIdColumn, nameColumn]; const patches: Array<{ columnId: string; body: Record }> = []; let requestInFlight = false; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get( "/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json(columns), ), http.patch( "/api/catalog/databases/:databaseId/tables/:tableId/columns/:columnId", async ({ params, request }) => { if (requestInFlight) { return HttpResponse.json({ code: "catalog_operation_active", message: "Another catalog operation is in progress.", }, { status: 409 }); } requestInFlight = true; const body = await request.json() as Record; await new Promise((resolve) => window.setTimeout(resolve, 20)); patches.push({ columnId: String(params.columnId), body }); requestInFlight = false; const current = columns.find((column) => column.id === params.columnId)!; return HttpResponse.json({ ...current, ...body, version: current.version + 1 }); }, ), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], 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", })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); expect(screen.queryByRole("button", { name: "Save sensitive fields" })).not.toBeInTheDocument(); await user.click(await screen.findByRole("checkbox", { name: "Sensitive data for id" })); await user.click(screen.getByRole("checkbox", { name: "Sensitive data for name" })); const saveButton = screen.getByRole("button", { name: "Save sensitive fields" }); expect(saveButton).toBeVisible(); await user.click(saveButton); await waitFor(() => expect(patches).toHaveLength(2)); expect(patches).toEqual(expect.arrayContaining([ expect.objectContaining({ columnId: patientIdColumn.id, body: expect.objectContaining({ version: patientIdColumn.version, sensitive: true }), }), expect.objectContaining({ columnId: nameColumn.id, body: expect.objectContaining({ version: nameColumn.version, sensitive: true }), }), ])); expect(await screen.findByText("Sensitive fields saved")).toBeVisible(); await waitFor(() => { expect(screen.queryByRole("button", { name: "Save sensitive fields" })).not.toBeInTheDocument(); }); }); test("shows a read-only sensitivity reason in the Fleet column metadata matrix", async () => { const user = userEvent.setup(); const sensitiveColumn: CatalogColumn = { ...patientIdColumn, sensitive: true, sensitivityReason: "Local assessment matched content rule pii.email.", }; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get( "/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([sensitiveColumn]), ), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], 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", })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); await user.click(await screen.findByRole("button", { name: "Edit metadata for id" })); const drawer = await screen.findByRole("dialog", { name: "Review column description" }); const matrix = within(drawer).getByRole("group", { name: "Column metadata fields" }); expect(matrix).toHaveClass("sm:grid-cols-2"); expect(within(matrix).getByLabelText("Source comment")).toHaveAttribute("readonly"); expect(within(matrix).getByLabelText("Description")).not.toHaveAttribute("readonly"); expect(within(matrix).getByLabelText("Generated description")).not.toHaveAttribute("readonly"); expect(within(matrix).getByLabelText("Sensitive motivation")).toHaveValue( "Local assessment matched content rule pii.email.", ); expect(within(matrix).getByLabelText("Sensitive motivation")).toHaveAttribute("readonly"); }); test("filters catalog tables as the operator types", async () => { const user = userEvent.setup(); const mediciTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "bridge_medici", sourceComment: "Doctors participating in clinical care", }; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([ patientsTable, mediciTable, ])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tablesRegion = await screen.findByRole("region", { name: "Tables for Policlinico San Donato" }); expect(await screen.findByRole("button", { name: "Edit description for patients" })).toBeVisible(); expect(screen.getByRole("button", { name: "Edit description for bridge_medici" })).toBeVisible(); await user.type(screen.getByRole("textbox", { name: "Search tables" }), "medici"); expect(screen.getByRole("button", { name: "Edit description for bridge_medici" })).toBeVisible(); expect(await within(tablesRegion).findByText("1 of 2")).toBeVisible(); await waitFor(() => { expect(screen.queryByRole("button", { name: "Edit description for patients" })).not.toBeInTheDocument(); }); }); test("selected table Actions expose table commands and send synchronization ids", async () => { const user = userEvent.setup(); let startBody: unknown; const run = makeSyncRun("columns", [patientsTable.id]); registerCompletedSyncRun(run); server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.post("/api/catalog/databases/:databaseId/sync-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(run, { status: 202 }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); const synchronizeColumns = await screen.findByRole("menuitem", { name: "Synchronize columns for selected tables" }); expect(synchronizeColumns).toBeEnabled(); expect(screen.getByRole("menuitem", { name: "Synchronize database tables" })).toBeEnabled(); expect(screen.getByRole("menuitem", { name: "Remove synchronized columns" })).toBeEnabled(); expect(screen.queryByRole("menuitem", { name: "Delete all relationships" })).not.toBeInTheDocument(); await user.click(synchronizeColumns); await waitFor(() => expect(startBody).toEqual({ version: 3, scope: "columns", tableIds: [patientsTable.id], })); await waitFor(() => expect(screen.queryByText("1 selected")).not.toBeInTheDocument()); }); test("starts description generation for multiple selected tables", async () => { const user = userEvent.setup(); const visitsTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "visits", }; const queuedRun = makeDescriptionGenerationRun({ scope: "selected_tables", total: 2, }); let startBody: unknown; 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([ patientsTable, visitsTable, ])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(within(await screen.findByRole("row", { name: /patients/ })).getByRole("checkbox")); await user.click(within(await screen.findByRole("row", { name: /visits/ })).getByRole("checkbox")); await user.click(screen.getByRole("button", { name: "Actions" })); const generate = await screen.findByRole("menuitem", { name: "Generate table description" }); expect(generate).toBeEnabled(); expect(screen.getByRole("menuitem", { name: "Copy generated descriptions to Description", })).toBeEnabled(); await user.click(generate); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "selected_tables", targetIds: [patientsTable.id, visitsTable.id], })); expect(await screen.findByRole("dialog", { name: "Description generation", })).toBeVisible(); expect(await screen.findByText("Description generation started for 2 tables")).toBeVisible(); expect(screen.getByText("2 selected")).toBeVisible(); }); test("generates descriptions for every column in selected tables", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun({ scope: "selected_columns", total: 1 }); let startBody: unknown; 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([patientsTable])), http.get(`/api/catalog/databases/:databaseId/tables/${patientsTable.id}/columns`, () => HttpResponse.json([patientIdColumn])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(within(await screen.findByRole("row", { name: /patients/ })).getByRole("checkbox")); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate descriptions for all columns" })); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "selected_columns", targetIds: [patientIdColumn.id], })); expect(await screen.findByText("Column description generation started for 1 column")).toBeVisible(); }); test("observes an active run from another browser and reopens a terminal run from history", async () => { const user = userEvent.setup(); const activeRun = makeDescriptionGenerationRun({ status: "running", startedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); const completedRun = makeDescriptionGenerationRun({ id: "88888888-8888-4888-8888-888888888888", scope: "missing", status: "completed", processed: 1, generated: 1, createdAt: "2026-08-28T09:00:00Z", startedAt: "2026-08-28T09:00:01Z", updatedAt: "2026-08-28T09:00:02Z", finishedAt: "2026-08-28T09:00:02Z", }); let requestedLimit: string | null = null; server.use( http.get("/api/catalog/description-generation-runs", ({ request }) => { requestedLimit = new URL(request.url).searchParams.get("limit"); return HttpResponse.json([activeRun, completedRun]); }), http.get("/api/catalog/description-generation-runs/:runId", ({ params }) => ( HttpResponse.json(params.runId === completedRun.id ? completedRun : activeRun) )), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage(); const observe = await screen.findByRole("button", { name: "View description generation history", }); expect(observe).toHaveTextContent("View description generation history"); expect(requestedLimit).toBe("50"); await user.click(observe); 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 sensitivity analysis history label stable while showing active status separately", async () => { const runningRun = makeSensitivityAnalysisRun({ 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: "View sensitivity analysis history", }); expect(historyButton).toHaveTextContent("View sensitivity analysis history"); await waitFor(() => expect(historyButton).toHaveAttribute( "title", "Sensitivity analysis is active", )); expect(historyButton.querySelector("[aria-hidden='true'].bg-primary")).not.toBeNull(); }); test.each([ "completed", "completed_with_errors", "failed", "cancelled", "interrupted", ] satisfies DescriptionGenerationRun["status"][])( "invalidates Catalog Tables and Catalog Columns for terminal status %s", async (status) => { const user = userEvent.setup(); const terminalRun = makeDescriptionGenerationRun({ status, finishedAt: "2026-08-28T10:00:02Z", }); server.use( http.get("/api/catalog/description-generation-runs", () => HttpResponse.json([terminalRun])), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(terminalRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); const { client } = renderPage(); const tableKey = ["catalog-tables", terminalRun.databaseId] as const; const columnKey = ["catalog-columns", terminalRun.databaseId, "table-id"] as const; const unrelatedColumnKey = ["catalog-columns", "other-database", "table-id"] as const; client.setQueryData(tableKey, []); client.setQueryData(columnKey, []); client.setQueryData(unrelatedColumnKey, []); await user.click(await screen.findByRole("button", { name: "View description generation history", })); await waitFor(() => expect(client.getQueryState(tableKey)?.isInvalidated).toBe(true)); expect(client.getQueryState(columnKey)?.isInvalidated).toBe(true); expect(client.getQueryState(unrelatedColumnKey)?.isInvalidated).toBe(false); }, ); test("keeps selected tables when description generation cannot start", async () => { const user = userEvent.setup(); const visitsTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "visits", }; 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([ patientsTable, visitsTable, ])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => HttpResponse.json({ code: "description_generation_run_active", message: "private provider detail", }, { status: 409 })), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(within(await screen.findByRole("row", { name: /patients/ })).getByRole("checkbox")); await user.click(within(await screen.findByRole("row", { name: /visits/ })).getByRole("checkbox")); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate table description" })); expect(await screen.findByText("A description generation run is already active.")).toBeVisible(); expect(screen.getByText("2 selected")).toBeVisible(); expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument(); expect(document.body).not.toHaveTextContent("private provider detail"); }); test("refreshes Catalog Tables and Catalog Columns after table generation completes", async () => { const user = userEvent.setup(); const visitsTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "visits", }; const queuedRun = makeDescriptionGenerationRun({ scope: "selected_tables", total: 2, }); const completedRun = makeDescriptionGenerationRun({ scope: "selected_tables", status: "completed", total: 2, processed: 2, generated: 2, startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:02Z", finishedAt: "2026-08-28T10:00:02Z", }); let tables = [patientsTable, visitsTable]; let tableReads = 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/databases/:databaseId/tables", () => { tableReads += 1; return HttpResponse.json(tables); }), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => { tables = tables.map((table) => ({ ...table, generatedDescription: `Generated ${table.name}`, version: table.version + 1, })); return HttpResponse.json(completedRun); }), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); const { client } = renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); const unrelatedTableQuery = ["catalog-tables", "other-database"] as const; const columnsQuery = [ "catalog-columns", patientsTable.databaseId, patientsTable.id, ] as const; client.setQueryData(unrelatedTableQuery, []); client.setQueryData(columnsQuery, []); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(within(await screen.findByRole("row", { name: /patients/ })).getByRole("checkbox")); await user.click(within(await screen.findByRole("row", { name: /visits/ })).getByRole("checkbox")); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Generate table description" })); expect(await screen.findByRole("heading", { name: "Completed" })).toBeVisible(); await waitFor(() => expect(tableReads).toBe(2)); expect(within(await screen.findByRole("row", { name: /patients/ })) .getByText("Generated patients")).toBeVisible(); expect(client.getQueryState(unrelatedTableQuery)?.isInvalidated).toBe(false); expect(client.getQueryState(columnsQuery)?.isInvalidated).toBe(true); }); test("moves selected generated table descriptions and reports copied and skipped counts", async () => { const user = userEvent.setup(); const visitsTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "visits", description: "Keep curated visits", generatedDescription: null, }; let tables = [ { ...patientsTable, description: "Old patients", generatedDescription: "Generated patients" }, visitsTable, ]; let consolidationBody: unknown; let tableReads = 0; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => { tableReads += 1; return HttpResponse.json(tables); }), http.post("/api/catalog/databases/:databaseId/descriptions/consolidate", async ({ request }) => { consolidationBody = await request.json(); tables = tables.map((table) => table.id === patientsTable.id ? { ...table, description: table.generatedDescription, version: table.version + 1 } : table); return HttpResponse.json({ copied: 1, skipped: 1 }); }), ); const { client } = renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); const unrelatedColumnQuery = [ "catalog-columns", patientsTable.databaseId, visitsTable.id, ] as const; client.setQueryData(unrelatedColumnQuery, []); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const patientsRow = await screen.findByRole("row", { name: /patients/ }); const visitsRow = await screen.findByRole("row", { name: /visits/ }); await user.click(within(patientsRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(visitsRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Copy generated descriptions to Description", })); await waitFor(() => expect(consolidationBody).toEqual({ target: "tables", targetIds: [patientsTable.id, visitsTable.id], })); expect(await screen.findByText("Copied 1 description; skipped 1")).toBeVisible(); await waitFor(() => expect(tableReads).toBe(2)); expect(within(await screen.findByRole("row", { name: /patients/ })) .getAllByText("Generated patients")).toHaveLength(2); expect(within(await screen.findByRole("row", { name: /visits/ })) .getByText("Keep curated visits")).toBeVisible(); expect(screen.queryByText("2 selected")).not.toBeInTheDocument(); expect(client.getQueryState(unrelatedColumnQuery)?.isInvalidated).toBe(false); }); test("selects columns, moves generated descriptions, and refreshes only the affected column query", async () => { const user = userEvent.setup(); const idColumn: CatalogColumn = { id: "dddddddd-dddd-4ddd-8ddd-dddddddddddd", tableId: patientsTable.id, name: "id", ordinalPosition: 1, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: 1, isPrimaryKey: true, isForeignKey: false, foreignKeyCount: 0, sourceComment: "Patient identifier", description: "Old identifier", generatedDescription: "Generated identifier", sensitive: false, lastSyncedDatabaseVersion: 3, lastSyncedAt: "2026-08-27T10:00:00Z", version: 1, createdAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:00Z", }; const nameColumn: CatalogColumn = { ...idColumn, id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", name: "name", ordinalPosition: 2, dataType: "text", primaryKeyPosition: null, isPrimaryKey: false, description: "Keep curated patient name", generatedDescription: "", }; let columns = [idColumn, nameColumn]; let consolidationBody: unknown; let columnReads = 0; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => { columnReads += 1; return HttpResponse.json(columns); }), http.post("/api/catalog/databases/:databaseId/descriptions/consolidate", async ({ request }) => { consolidationBody = await request.json(); columns = columns.map((column) => column.id === idColumn.id ? { ...column, description: column.generatedDescription, version: column.version + 1 } : column); return HttpResponse.json({ copied: 1, skipped: 1 }); }), ); const { client } = renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); const tableQuery = ["catalog-tables", patientsTable.databaseId] as const; const selectableRow = async (name: RegExp) => { const rows = await screen.findAllByRole("row", { name }); const row = rows.find((candidate) => within(candidate).queryByRole("checkbox", { name: /toggle row selection/i })); expect(row).toBeDefined(); return row!; }; await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const idRow = await selectableRow(/Patient identifier/); const nameRow = await selectableRow(/Keep curated patient name/); await user.click(within(idRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(nameRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Synchronize columns for this table" })).toBeEnabled(); await user.click(await screen.findByRole("menuitem", { name: "Copy generated descriptions to Description", })); await waitFor(() => expect(consolidationBody).toEqual({ target: "columns", targetIds: [idColumn.id, nameColumn.id], })); expect(await screen.findByText("Copied 1 description; skipped 1")).toBeVisible(); await waitFor(() => expect(columnReads).toBe(2)); expect(within(await selectableRow(/Patient identifier/)) .getAllByText("Generated identifier")).toHaveLength(2); expect(within(await selectableRow(/Keep curated patient name/)) .getByText("Keep curated patient name")).toBeVisible(); expect(screen.queryByText("2 selected")).not.toBeInTheDocument(); expect(client.getQueryState(tableQuery)?.isInvalidated).toBe(false); }); test("starts one selected column with the configured default model", async () => { const user = userEvent.setup(); let startBody: unknown; const queuedRun = makeDescriptionGenerationRun(); server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [ { id: "openai/mini", label: "OpenAI Mini" }, { id: "local/qwen", label: "Local Qwen" }, ], default: "local/qwen", })), http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([patientIdColumn])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); expect(screen.queryByRole("button", { name: "Sync columns" })).not.toBeInTheDocument(); expect(screen.getByRole("columnheader", { name: /Select/i })).toBeVisible(); expect(await screen.findByRole("checkbox", { name: "Sensitive data for id" })).toHaveAttribute( "title", "Mark this column as sensitive; this does not select it for actions.", ); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); expect(columnRow).toBeDefined(); 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 column description" })); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "selected_columns", targetIds: [patientIdColumn.id], })); 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(); }); test("shows database sensitivity analysis only for a selection and rejects multiple databases clearly", async () => { const user = userEvent.setup(); let suggestionCalls = 0; const radiology = makeDatabase({ id: "44444444-4444-4444-8444-444444444444", workspaceId: "radiology", workspaceName: "Radiology", }); server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "local/qwen", label: "Local Qwen" }], default: "local/qwen", })), http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", () => { suggestionCalls += 1; return HttpResponse.json({ suggestions: [] }); }), ); renderPage({ rows: [makeDatabase(), radiology] }); expect(screen.queryByRole("button", { name: "Analyze sensitive fields" })).not.toBeInTheDocument(); const psdRow = await screen.findByRole("row", { name: /Policlinico San Donato/ }); const radiologyRow = await screen.findByRole("row", { name: /Radiology/ }); await user.click(within(psdRow).getByRole("checkbox", { name: /toggle row selection/i })); expect(screen.getByRole("button", { name: "Analyze sensitive fields" })).toBeVisible(); await user.click(within(radiologyRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Analyze sensitive fields" })); expect(await screen.findByText("Sensitivity analysis can run for only one database at a time. Select one database and try again.")).toBeVisible(); expect(suggestionCalls).toBe(0); }); test("opens sensitivity analysis progress while the assessment request is still running", async () => { const user = userEvent.setup(); let analysisStarted = false; let releaseAnalysis!: () => void; const analysisGate = new Promise((resolve) => { releaseAnalysis = resolve; }); const runningRun = makeSensitivityAnalysisRun({ scope: "all", status: "running", total: 2, suggestedSensitive: 0, suggestedNonSensitive: 0, unknown: 2, finishedAt: null, }); const completedRun = makeSensitivityAnalysisRun({ scope: "all", total: 2 }); let activityEvents = [{ runId: runningRun.id, sequence: 1, level: "info", message: "Scanning source columns", createdAt: "2026-08-28T11:00:00Z", }]; server.use( http.get("/api/catalog/sensitive-data-suggestion-runs", () => ( HttpResponse.json(analysisStarted ? [runningRun] : []) )), http.get("/api/catalog/sensitive-data-suggestion-runs/:runId", () => ( HttpResponse.json(runningRun) )), http.get("/api/catalog/sensitive-data-suggestion-runs/:runId/events-list", () => ( HttpResponse.json(activityEvents) )), http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", async () => { analysisStarted = true; await analysisGate; return HttpResponse.json({ run: completedRun, suggestions: [] }); }), ); 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 })); const launch = user.click(screen.getByRole("button", { name: "Analyze sensitive fields" })); await waitFor(() => expect(analysisStarted).toBe(true)); const progress = await screen.findByRole("dialog", { name: "Sensitivity analysis history" }); expect(within(progress).getByRole("heading", { name: "Starting sensitivity analysis" })).toBeVisible(); expect(within(progress).getByRole("progressbar", { name: "Sensitivity analysis is starting" })) .toBeVisible(); expect(within(progress).getByText("Preparing sensitivity analysis…")).toBeVisible(); expect(await within(progress).findByRole("heading", { name: "Running" }, { timeout: 2_000 })).toBeVisible(); expect(await within(progress).findByText("Scanning source columns")).toBeVisible(); const eventLog = within(progress).getByRole("log", { name: "Sensitivity analysis events" }); Object.defineProperties(eventLog, { clientHeight: { configurable: true, value: 100 }, scrollHeight: { configurable: true, get: () => activityEvents.length * 100 }, scrollTop: { configurable: true, value: 0, writable: true }, }); activityEvents = [...activityEvents, { runId: runningRun.id, sequence: 2, level: "info", message: "Running local entity detection", createdAt: "2026-08-28T11:00:01Z", }]; expect(await within(progress).findByText("Running local entity detection", {}, { timeout: 2_000 })).toBeVisible(); await waitFor(() => expect(eventLog.scrollTop).toBe(eventLog.scrollHeight)); eventLog.scrollTop = 0; fireEvent.scroll(eventLog); const jumpToLatest = await within(progress).findByRole("button", { name: "Jump to latest" }); activityEvents = [...activityEvents, { runId: runningRun.id, sequence: 3, level: "info", message: "Classifying the next table", createdAt: "2026-08-28T11:00:02Z", }]; expect(await within(progress).findByText("Classifying the next table", {}, { timeout: 2_000 })).toBeVisible(); expect(eventLog.scrollTop).toBe(0); await user.click(jumpToLatest); expect(eventLog.scrollTop).toBe(eventLog.scrollHeight); expect(within(progress).queryByRole("button", { name: "Jump to latest" })).not.toBeInTheDocument(); releaseAnalysis(); await launch; expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible(); }); test("requests database-level sensitivity analysis for the only selected database", async () => { const user = userEvent.setup(); let suggestionBody: unknown; let suggestionFinished = false; let historyCalls = 0; const run = makeSensitivityAnalysisRun({ 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, tableName: patientsTable.name, columnName: patientIdColumn.name, version: patientIdColumn.version, currentSensitive: false, sensitive: true, assessment: "sensitive", evidence: [{ kind: "content", ruleId: "pii.email" }], observedValues: 1, coverage: "sampled", }], }); }), ); 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: "Analyze sensitive fields" })); await waitFor(() => expect(suggestionBody).toEqual({ scope: "all" })); expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible(); await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2)); }); test("refetches sensitivity analysis 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: "sensitivity_source_scan_failed", message: "Sensitivity analysis could not read the source. No assessments 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: "Analyze sensitive fields" })); await waitFor(() => expect(historyCalls).toBeGreaterThanOrEqual(2)); expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument(); }); test("requests sensitivity analysis only for selected tables", async () => { const user = userEvent.setup(); const visitsTable: CatalogTable = { ...patientsTable, id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", name: "visits", }; const visitColumn = { ...patientIdColumn, id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", tableId: visitsTable.id, name: "clinical_note", }; let suggestionBody: unknown; 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([ patientsTable, visitsTable, ])), http.post("/api/catalog/databases/:databaseId/sensitive-data-suggestions", async ({ request }) => { suggestionBody = await request.json(); return HttpResponse.json({ suggestions: [{ columnId: visitColumn.id, tableId: visitsTable.id, tableName: visitsTable.name, columnName: visitColumn.name, version: visitColumn.version, currentSensitive: false, sensitive: true, assessment: "sensitive", evidence: [{ kind: "metadata", ruleId: "metadata.health" }], observedValues: 0, coverage: "metadata", }], }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const visitsRow = await screen.findByRole("row", { name: /visits/ }); await user.click(within(visitsRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Analyze sensitive fields" })); await waitFor(() => expect(suggestionBody).toEqual({ scope: "selected_tables", targetIds: [visitsTable.id], })); expect(await screen.findByRole("dialog", { name: "Sensitive field review" })).toBeVisible(); }); test("allows a human downgrade and saves only explicit sensitivity changes", async () => { const user = userEvent.setup(); const idColumn = { ...patientIdColumn, sensitive: false }; const nameColumn = { ...patientIdColumn, id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", name: "name", ordinalPosition: 2, primaryKeyPosition: null, isPrimaryKey: false, description: "Patient name", generatedDescription: "Name of the patient", sensitive: true, }; const unselectedColumn = { ...patientIdColumn, id: "ffffffff-ffff-4fff-8fff-ffffffffffff", name: "address", ordinalPosition: 3, primaryKeyPosition: null, isPrimaryKey: false, description: "Patient address", generatedDescription: "Address of the patient", sensitive: false, }; let columns = [idColumn, nameColumn, unselectedColumn]; let suggestionBody: unknown; const patches: Array<{ columnId: string; body: unknown }> = []; 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([patientsTable])), http.get( "/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json(columns), ), http.post( "/api/catalog/databases/:databaseId/sensitive-data-suggestions", async ({ request }) => { suggestionBody = await request.json(); return HttpResponse.json({ suggestions: [ { columnId: idColumn.id, tableId: patientsTable.id, tableName: patientsTable.name, columnName: idColumn.name, version: idColumn.version, currentSensitive: false, sensitive: true, assessment: "sensitive", evidence: [{ kind: "content", ruleId: "pii.email" }], observedValues: 1, coverage: "sampled", }, { columnId: nameColumn.id, tableId: patientsTable.id, tableName: patientsTable.name, columnName: nameColumn.name, version: nameColumn.version, currentSensitive: true, sensitive: false, assessment: "non_sensitive", evidence: [{ kind: "coverage", ruleId: "coverage.complete" }], observedValues: 2, coverage: "complete", }, ], }); }, ), http.patch( "/api/catalog/databases/:databaseId/tables/:tableId/columns/:columnId", async ({ params, request }) => { const body = await request.json() as { version: number; sensitive: boolean; }; patches.push({ columnId: String(params.columnId), body }); const current = columns.find((column) => column.id === params.columnId)!; const updated = { ...current, ...body, version: current.version + 1 }; columns = columns.map((column) => column.id === updated.id ? updated : column); return HttpResponse.json(updated); }, ), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const idSensitive = await screen.findByRole("checkbox", { name: "Sensitive data for id" }); const nameSensitive = await screen.findByRole("checkbox", { name: "Sensitive data for name" }); expect(idSensitive).not.toBeChecked(); expect(nameSensitive).toBeChecked(); expect(screen.queryByRole("button", { name: "Analyze sensitive fields" })).not.toBeInTheDocument(); const selectableRow = async (name: RegExp) => { const rows = await screen.findAllByRole("row", { name }); return rows.find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i }))!; }; await user.click(within(await selectableRow(/Patient identifier/)) .getByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(await selectableRow(/Patient name/)) .getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Analyze sensitive fields" })); await waitFor(() => expect(suggestionBody).toEqual({ scope: "selected_columns", targetIds: [idColumn.id, nameColumn.id], })); 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" })).not.toBeChecked(); expect(patches).toHaveLength(0); await user.click(within(review).getByRole("checkbox", { name: "Protect patients.id" })); await user.click(within(review).getByRole("button", { name: "Save all 1 change" })); await waitFor(() => expect(patches).toEqual([{ columnId: nameColumn.id, body: { version: nameColumn.version, sensitive: false, sensitivityReason: null, }, }])); expect(await screen.findByText("Saved 1 sensitive flag")).toBeVisible(); await waitFor(() => expect(screen.queryByRole("dialog", { name: "Sensitive field review" })).not.toBeInTheDocument()); await waitFor(() => expect(screen.getByRole("checkbox", { name: "Sensitive data for id" })).not.toBeChecked()); expect(screen.getByRole("checkbox", { name: "Sensitive data for name" })).not.toBeChecked(); }); test("polls a description run and renders its events in sequence order", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun(); const runningRun = makeDescriptionGenerationRun({ status: "running", startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:01Z", }); const completedRun = makeDescriptionGenerationRun({ status: "completed", processed: 1, generated: 1, startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:03Z", finishedAt: "2026-08-28T10:00:03Z", }); let runReads = 0; const eventAfters: number[] = []; 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([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([patientIdColumn])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => { runReads += 1; return HttpResponse.json(runReads < 3 ? runningRun : completedRun); }), http.get("/api/catalog/description-generation-runs/:runId/events-list", ({ request }) => { const after = Number(new URL(request.url).searchParams.get("after") ?? 0); eventAfters.push(after); if (after === 0) return HttpResponse.json([ { sequence: 2, level: "info", message: "Calling the selected model", createdAt: "2026-08-28T10:00:02Z" }, { sequence: 1, level: "info", message: "Run queued", createdAt: "2026-08-28T10:00:00Z" }, ]); if (after === 2 && runReads >= 3) return HttpResponse.json([ { sequence: 3, level: "info", message: "Generated description saved", createdAt: "2026-08-28T10:00:03Z" }, ]); return HttpResponse.json([]); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); 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 column description" })); 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(); expect(within(drawer).getByText("1", { selector: "[data-count='total']" })).toBeVisible(); expect(within(drawer).getByText("1", { selector: "[data-count='processed']" })).toBeVisible(); expect(within(drawer).getByText("1", { selector: "[data-count='generated']" })).toBeVisible(); expect(within(drawer).getByText("0", { selector: "[data-count='nonGeneratable']" })).toBeVisible(); expect(within(drawer).getByText("0", { selector: "[data-count='failed']" })).toBeVisible(); const timestamps = within(drawer).getByRole("region", { name: "Description generation timestamps", }); expect(within(timestamps).getByText("Created")).toBeVisible(); expect(within(timestamps).getByText("Started")).toBeVisible(); expect(within(timestamps).getByText("Updated")).toBeVisible(); expect(within(timestamps).getByText("Finished")).toBeVisible(); const log = within(drawer).getByRole("log", { name: "Description generation events" }); await waitFor(() => expect(log).toHaveTextContent("Generated description saved"), { timeout: 4_000 }); expect(log.textContent!.indexOf("Run queued")).toBeLessThan(log.textContent!.indexOf("Calling the selected model")); expect(log.textContent!.indexOf("Calling the selected model")).toBeLessThan(log.textContent!.indexOf("Generated description saved")); expect(eventAfters).toContain(2); }); test("refreshes Catalog Tables and Catalog Columns after column generation completes", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun(); const completedRun = makeDescriptionGenerationRun({ status: "completed", processed: 1, generated: 1, startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:02Z", finishedAt: "2026-08-28T10:00:02Z", }); let columnReads = 0; let tableReads = 0; let columns = [patientIdColumn]; 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", () => { tableReads += 1; return HttpResponse.json([patientsTable]); }), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => { columnReads += 1; return HttpResponse.json(columns); }), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => { columns = [{ ...patientIdColumn, generatedDescription: "Stable patient identifier", version: 2, }]; return HttpResponse.json(completedRun); }), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); const { client } = renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); const unrelatedColumnsQuery = [ "catalog-columns", patientsTable.databaseId, "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", ] as const; client.setQueryData(unrelatedColumnsQuery, []); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); 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 column description" })); expect(await screen.findByRole("heading", { name: "Completed" })).toBeVisible(); await waitFor(() => expect(columnReads).toBe(2)); await waitFor(() => expect(tableReads).toBe(2)); expect(within((await screen.findAllByRole("row", { name: /Stable patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i }))!).getByText("Stable patient identifier")).toBeVisible(); expect(client.getQueryState(unrelatedColumnsQuery)?.isInvalidated).toBe(true); }); test("keeps the selected column and shows a safe message when generation cannot start", 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([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([patientIdColumn])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => HttpResponse.json({ code: "description_generation_run_active", message: "provider=private-provider key=sk-private-key", }, { status: 409 })), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); 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 column description" })); expect(await screen.findByText("A description generation run is already active.")).toBeVisible(); expect(screen.getByText("1 selected")).toBeVisible(); expect(screen.queryByRole("dialog", { name: "Description generation" })).not.toBeInTheDocument(); expect(document.body).not.toHaveTextContent(/private-provider|sk-private-key/); }); test("shows a basic failed run without exposing private model or provider fields", async () => { const user = userEvent.setup(); let columnReads = 0; const queuedRun = { ...makeDescriptionGenerationRun({ modelId: "approved/model" }), provider: "private-provider", apiKey: "sk-private-key", }; const failedRun = { ...makeDescriptionGenerationRun({ modelId: "approved/model", status: "failed", processed: 1, failed: 1, startedAt: "2026-08-28T10:00:01Z", updatedAt: "2026-08-28T10:00:02Z", finishedAt: "2026-08-28T10:00:02Z", errorSummary: "The model request failed before a description was saved.", }), provider: "private-provider", upstreamModel: "private/upstream-model", apiKey: "sk-private-key", prompt: "full private prompt", samples: ["private-sample-value"], providerPayload: { diagnostic: "private provider payload" }, }; server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models: [{ id: "approved/model", label: "Approved model", provider: "private-provider", upstreamModel: "private/upstream-model", apiKeyEnv: "PRIVATE_MODEL_API_KEY", }], default: "approved/model", })), http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => { columnReads += 1; return HttpResponse.json([patientIdColumn]); }), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(failedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([ { sequence: 1, level: "error", message: "Description generation failed", createdAt: "2026-08-28T10:00:02Z" }, ])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); 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 column description" })); 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(); expect(await within(drawer).findByText("Description generation failed")).toBeVisible(); await waitFor(() => expect(columnReads).toBe(2)); expect(document.body).not.toHaveTextContent( /private-provider|private\/upstream-model|sk-private-key|full private prompt|private-sample-value|private provider payload|PRIVATE_MODEL_API_KEY/, ); }); test("offers generation and consolidation for multiple selected columns", async () => { const user = userEvent.setup(); const patientNameColumn: CatalogColumn = { ...patientIdColumn, id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", name: "name", ordinalPosition: 2, dataType: "text", primaryKeyPosition: null, isPrimaryKey: false, sourceComment: "Patient name", }; const queuedRun = makeDescriptionGenerationRun({ total: 2 }); let startBody: unknown; 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([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => ( HttpResponse.json([patientIdColumn, patientNameColumn]) )), http.post("/api/catalog/databases/:databaseId/description-generation-runs", async ({ request }) => { startBody = await request.json(); return HttpResponse.json(queuedRun, { status: 202 }); }), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json(queuedRun)), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const idRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); const nameRow = (await screen.findAllByRole("row", { name: /Patient name/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(idRow!).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate column description" })).toBeEnabled(); expect(screen.getByRole("menuitem", { name: "Copy generated descriptions to Description", })).toBeEnabled(); await user.keyboard("{Escape}"); await user.click(within(nameRow!).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); const generate = await screen.findByRole("menuitem", { name: "Generate column descriptions" }); expect(generate).toBeEnabled(); expect(await screen.findByRole("menuitem", { name: "Copy generated descriptions to Description", })).toBeEnabled(); await user.click(generate); await waitFor(() => expect(startBody).toEqual({ modelId: "local/qwen", scope: "selected_columns", targetIds: [patientIdColumn.id, patientNameColumn.id], })); expect(await screen.findByText("Description generation started for 2 columns")).toBeVisible(); expect(screen.getByText("2 selected")).toBeVisible(); }); test.each([ { name: "database.manage is absent", canManage: false, models: [{ id: "local/qwen", label: "Local Qwen" }], configuredDefault: "local/qwen" }, { name: "no usable model is configured", canManage: true, models: [], configuredDefault: null }, ])("disables selected table and column generation when $name", async ({ canManage, models, configuredDefault }) => { const user = userEvent.setup(); server.use( http.get("/api/catalog/metadata-generation/models", () => HttpResponse.json({ models, default: configuredDefault, })), http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([patientIdColumn])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], canManage, }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate descriptions for all columns" })) .toHaveAttribute("aria-disabled", "true"); await user.keyboard("{Escape}"); await user.click(screen.getByRole("button", { name: "Clear" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); await user.click(within(columnRow!).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate column description" })) .toHaveAttribute("aria-disabled", "true"); }); test("disables another generation start while the selected-column run is active", async () => { const user = userEvent.setup(); const queuedRun = makeDescriptionGenerationRun(); 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([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([patientIdColumn])), http.post("/api/catalog/databases/:databaseId/description-generation-runs", () => ( HttpResponse.json(queuedRun, { status: 202 }) )), http.get("/api/catalog/description-generation-runs/:runId", () => HttpResponse.json({ ...queuedRun, status: "running", startedAt: "2026-08-28T10:00:01Z", })), http.get("/api/catalog/description-generation-runs/:runId/events-list", () => HttpResponse.json([])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); const columnRow = (await screen.findAllByRole("row", { name: /Patient identifier/ })) .find((row) => within(row).queryByRole("checkbox", { name: /toggle row selection/i })); 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 column description" })); expect(await screen.findByRole("dialog", { name: "Description generation" })).toBeVisible(); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate column description" })) .toHaveAttribute("aria-disabled", "true"); expect(screen.getByText("1 selected")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Back to tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Generate descriptions for all columns" })) .toHaveAttribute("aria-disabled", "true"); }); test("disables selected description consolidation without database.manage", async () => { const user = userEvent.setup(); let consolidationCalls = 0; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([ { ...patientsTable, generatedDescription: "Generated patients" }, ])), http.post("/api/catalog/databases/:databaseId/descriptions/consolidate", () => { consolidationCalls += 1; return HttpResponse.json({ copied: 1, skipped: 0 }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], canManage: false, }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(await screen.findByRole("menuitem", { name: "Copy generated descriptions to Description", })).toHaveAttribute("aria-disabled", "true"); expect(consolidationCalls).toBe(0); }); test("shows an operation conflict without clearing selected descriptions or refreshing tables", async () => { const user = userEvent.setup(); let tableReads = 0; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => { tableReads += 1; return HttpResponse.json([{ ...patientsTable, generatedDescription: "Generated patients" }]); }), http.post("/api/catalog/databases/:databaseId/descriptions/consolidate", () => HttpResponse.json({ code: "database_operation_in_progress", message: "A database operation is already in progress.", }, { status: 409 })), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Copy generated descriptions to Description", })); expect(await screen.findByText("A database operation is already in progress.")).toBeVisible(); expect(screen.getByText("1 selected")).toBeVisible(); expect(tableReads).toBe(1); }); test("selected table Actions do not expose relationship cleanup", async () => { const user = userEvent.setup(); server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); const tableRow = await screen.findByRole("row", { name: /patients/ }); await user.click(within(tableRow).getByRole("checkbox", { name: /toggle row selection/i })); await user.click(screen.getByRole("button", { name: "Actions" })); expect(screen.queryByRole("menuitem", { name: "Delete all relationships" })).not.toBeInTheDocument(); expect(screen.queryByRole("menuitem", { name: "Clear catalog relationships" })).not.toBeInTheDocument(); }); test("navigates purely from a database to its tables and edits review metadata", async () => { const user = userEvent.setup(); let patchBody: unknown; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.patch("/api/catalog/databases/:databaseId/tables/:tableId", async ({ request }) => { patchBody = await request.json(); return HttpResponse.json({ ...patientsTable, description: "Registry used for longitudinal patient analysis", version: 2, }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); expect(await screen.findByRole("region", { name: "Tables for Policlinico San Donato" })).toBeVisible(); expect(screen.getByRole("tab", { name: "Tables" })).toHaveAttribute("aria-selected", "true"); expect(screen.queryByLabelText("Database filter")).not.toBeInTheDocument(); await user.click(await screen.findByRole("button", { name: "Edit description for patients" })); expect(screen.getByLabelText("Physical table name")).toHaveValue("patients"); expect(screen.getByLabelText("Physical table name")).toHaveAttribute("readonly"); expect(screen.getByLabelText("Source comment")).toHaveAttribute("readonly"); await user.type( screen.getByLabelText(/^Description/), "Registry used for longitudinal patient analysis", ); await user.click(screen.getByRole("button", { name: "Save metadata" })); await waitFor(() => expect(patchBody).toEqual({ version: 1, description: "Registry used for longitudinal patient analysis", generatedDescription: null, })); await user.click(screen.getByRole("button", { name: "Back to tables" })); await user.click(screen.getByRole("tab", { name: "Overview" })); expect(await screen.findByRole("heading", { name: "Database details" })).toBeVisible(); }); test("navigates from a table to columns and from the database to physical relationships", async () => { const user = userEvent.setup(); let columnPatch: unknown; const idColumn: CatalogColumn = { id: "dddddddd-dddd-4ddd-8ddd-dddddddddddd", tableId: patientsTable.id, name: "id", ordinalPosition: 1, dataType: "bigint", isNullable: false, defaultExpression: null, primaryKeyPosition: 1, isPrimaryKey: true, isForeignKey: true, foreignKeyCount: 1, sourceComment: "Patient identifier", description: null, generatedDescription: null, sensitive: false, lastSyncedDatabaseVersion: 3, lastSyncedAt: "2026-08-27T10:00:00Z", version: 1, createdAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:00Z", }; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.get("/api/catalog/databases/:databaseId/tables/:tableId/columns", () => HttpResponse.json([idColumn])), http.patch("/api/catalog/databases/:databaseId/tables/:tableId/columns/:columnId", async ({ request }) => { columnPatch = await request.json(); return HttpResponse.json({ ...idColumn, generatedDescription: "Generated identifier draft", version: 2 }); }), http.get("/api/catalog/databases/:databaseId/relationships", () => HttpResponse.json([{ id: "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", databaseId: patientsTable.databaseId, constraintName: "visits_patient_id_fkey", sourceTableId: "ffffffff-ffff-4fff-8fff-ffffffffffff", sourceTableName: "visits", targetTableId: patientsTable.id, targetTableName: "patients", updateRule: "NO ACTION", deleteRule: "CASCADE", deferrable: false, initiallyDeferred: false, columns: [{ position: 1, sourceColumnId: "1", sourceColumnName: "patient_id", targetColumnId: idColumn.id, targetColumnName: "id" }], origin: "physical", status: "active", lastSyncedDatabaseVersion: 3, lastSyncedAt: "2026-08-27T10:00:00Z", createdAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:00Z", }])), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })] }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "View columns for patients" })); expect(screen.getByRole("tab", { name: "Columns" })).toHaveAttribute("aria-selected", "true"); expect(await screen.findByText("PK")).toBeVisible(); expect(screen.getByText("FK")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Edit metadata for id" })); await user.type(screen.getByLabelText("Generated description"), "Generated identifier draft"); await user.click(screen.getByRole("button", { name: "Save metadata" })); await waitFor(() => expect(columnPatch).toEqual({ version: 1, description: null, generatedDescription: "Generated identifier draft", })); await user.click(screen.getByRole("tab", { name: "Relationships" })); expect(await screen.findByText("visits_patient_id_fkey")).toBeVisible(); }); test("opens the durable job drawer and confirms its exact destructive plan", async () => { const user = userEvent.setup(); const startBodies: unknown[] = []; const confirmationBodies: unknown[] = []; const queued: CatalogSyncRun = { id: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", databaseId: "11111111-1111-4111-8111-111111111111", scope: "tables", tableIds: [], state: "queued", phase: "queued", requestedDatabaseVersion: 3, plannedDiff: null, confirmationToken: null, counts: {}, errorCode: null, errorMessage: null, cancelRequested: false, createdAt: "2026-08-27T10:00:00Z", startedAt: null, updatedAt: "2026-08-27T10:00:00Z", finishedAt: null, heartbeatAt: null, }; const waiting: CatalogSyncRun = { ...queued, state: "awaiting_confirmation", phase: "awaiting_confirmation", confirmationToken: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", plannedDiff: { deletedTables: ["legacy_visits"], deletedColumns: [], deletedRelationships: [] }, counts: { tables: 1 }, startedAt: "2026-08-27T10:00:00Z", updatedAt: "2026-08-27T10:00:01Z", }; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([patientsTable])), http.post("/api/catalog/databases/:databaseId/sync-runs", async ({ request }) => { startBodies.push(await request.json()); return HttpResponse.json(queued, { status: 202 }); }), http.get("/api/catalog/sync-runs/:runId", () => HttpResponse.json(waiting)), http.get("/api/catalog/sync-runs/:runId/events-list", () => HttpResponse.json([])), http.get("/api/catalog/databases/:databaseId/sync-runs", () => HttpResponse.json([waiting])), http.post("/api/catalog/sync-runs/:runId/confirm", async ({ request }) => { confirmationBodies.push(await request.json()); return HttpResponse.json({ ...waiting, state: "queued", phase: "queued", confirmationToken: null }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); 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: "Actions" })); await user.click(await screen.findByRole("menuitem", { name: "Synchronize tables" })); const synchronizationDrawer = await screen.findByRole("dialog", { name: "Schema synchronization", }); 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"); expect(await screen.findByText("table · legacy_visits")).toBeVisible(); expect(startBodies).toEqual([{ version: 3, scope: "tables", tableIds: [] }]); await user.click(screen.getByRole("button", { name: "Confirm removals" })); await waitFor(() => expect(confirmationBodies).toEqual([ { confirmationToken: "cccccccc-cccc-4ccc-8ccc-cccccccccccc" }, ])); }); test("keeps a stale table draft but requires an explicit reload before another save", async () => { const user = userEvent.setup(); const current = { ...patientsTable, description: "Description saved by another editor", version: 2, }; let latest = patientsTable; server.use( http.get("/api/catalog/databases/:databaseId/tables", () => HttpResponse.json([latest])), http.patch("/api/catalog/databases/:databaseId/tables/:tableId", () => { latest = current; return HttpResponse.json({ code: "table_stale", message: "Table description changed. Reload and try again.", }, { status: 409 }); }), ); renderPage({ rows: [makeDatabase({ connectionStatus: "reachable", testedVersion: 3 })], }); await user.click(await screen.findByRole("button", { name: "View Policlinico San Donato" })); await user.click(screen.getByRole("tab", { name: "Tables" })); await user.click(await screen.findByRole("button", { name: "Edit description for patients" })); const description = screen.getByLabelText(/^Description/); await user.type(description, "My unsaved draft"); await user.click(screen.getByRole("button", { name: "Save metadata" })); expect(await screen.findByText("A newer table description is available.")).toBeVisible(); expect(description).toHaveValue("My unsaved draft"); expect(screen.getByRole("button", { name: "Save metadata" })).toBeDisabled(); await user.click(screen.getByRole("button", { name: "Keep editing" })); expect(screen.getByText("Reload the latest value before this description can be saved.")).toBeVisible(); await user.click(screen.getByRole("button", { name: "Reload latest" })); await waitFor(() => expect(description).toHaveValue("Description saved by another editor")); expect(screen.getByRole("button", { name: "Save metadata" })).toBeDisabled(); });