feat: implement metadata catalog database management

This commit is contained in:
Codex
2026-08-27 22:43:54 +02:00
parent 705af3aeb2
commit 79c4c925b5
86 changed files with 12566 additions and 135 deletions
@@ -0,0 +1,842 @@
import { act, render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { http, HttpResponse } from "msw";
import { server } from "../test/msw";
import type { CatalogColumn, CatalogDatabase, CatalogSyncRun, CatalogTable } from "../api/catalog-databases";
import { DatabaseManagementPage } from "./DatabaseManagementPage";
const noSecrets = {
password: false,
apiKey: false,
sshPrivateKey: false,
sshPrivateKeyPassphrase: false,
sshKnownHosts: false,
tlsCa: false,
};
function makeDatabase(overrides: Partial<CatalogDatabase> = {}): CatalogDatabase {
return {
id: "11111111-1111-4111-8111-111111111111",
workspaceId: "psd-clinical",
workspaceName: "Policlinico San Donato",
workspaceAvailable: 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,
});
function renderPage({
rows = [makeDatabase(), unconfigured, orphan],
canManage = true,
canManageSecrets = true,
onNavigationStateChange,
}: {
rows?: CatalogDatabase[] | (() => CatalogDatabase[]);
canManage?: boolean;
canManageSecrets?: boolean;
onNavigationStateChange?: (state: { dirty: boolean; busy: boolean }) => void;
} = {}) {
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(
<QueryClientProvider client={client}>
<DatabaseManagementPage
canManage={canManage}
canManageSecrets={canManageSecrets}
onNavigationStateChange={onNavigationStateChange}
/>
</QueryClientProvider>,
);
return { ...view, client };
}
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 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: "columns", label: "Synchronize all columns" },
{ scope: "relationships", label: "Synchronize relationships" },
{ scope: "all", label: "Synchronize all" },
] as const;
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: "Edit Research laboratory" })).toBeEnabled();
expect(screen.getByRole("button", { name: "Delete Research laboratory" })).toBeDisabled();
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"));
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: [] }));
},
);
test("presents completed synchronization steps as success and skips unneeded confirmation", async () => {
const user = userEvent.setup();
const run = {
...makeSyncRun("columns"),
counts: { tables: 163, columns: 2_275 },
};
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"));
await user.click(screen.getByRole("button", { name: "Actions" }));
await user.click(await screen.findByRole("menuitem", { name: "Synchronize all columns" }));
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 every synchronization scope", 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("opens an unconfigured row as Edit while creating its first saved configuration", 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: "Edit Research laboratory" }));
expect(screen.getByRole("heading", { name: "Edit database" })).toBeVisible();
expect(screen.getByLabelText("Workspace")).toBeDisabled();
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 database" }));
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("global Add offers only unconfigured workspaces and lets the operator choose one", async () => {
const user = userEvent.setup();
const second = makeDatabase({
...unconfigured,
workspaceId: "radiology",
workspaceName: "Radiology",
databaseName: "radiology_dwh",
});
renderPage({ rows: [makeDatabase(), unconfigured, second] });
await user.click(await screen.findByRole("button", { name: "Add database" }));
expect(screen.getByRole("heading", { name: "Add database" })).toBeVisible();
expect(screen.getByRole("button", { name: "Add database" })).toBeVisible();
const selector = screen.getByLabelText("Workspace");
expect(selector).toBeEnabled();
expect(screen.queryByRole("option", { name: "Policlinico San Donato" })).not.toBeInTheDocument();
await user.selectOptions(selector, "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: "Delete Policlinico San Donato" })).toBeDisabled();
expect(screen.getByText("Not configured")).toBeVisible();
});
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 });
return HttpResponse.json(row);
}));
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 }));
expect(within(screen.getByRole("region", { name: "Database details form" })).getByText("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<string, unknown> | undefined;
let row = makeDatabase();
server.use(
http.patch("/api/catalog/databases/:id", async ({ request }) => {
patchBody = await request.json() as Record<string, unknown>;
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<string, unknown>).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",
};
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 exposes a direct Synchronize columns action and sends its id", 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"));
const synchronizeColumns = screen.getByRole("button", { name: "Synchronize columns" });
expect(synchronizeColumns).toBeVisible();
expect(synchronizeColumns).toBeEnabled();
expect(screen.queryByRole("button", { name: "Actions" })).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("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,
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" }],
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 })],
});
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: "Sync tables" }));
const synchronizationDrawer = await screen.findByRole("complementary", {
name: "Schema synchronization",
});
expect(synchronizationDrawer).toHaveClass("inset-y-2", "sm:inset-y-4");
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();
});