280 lines
10 KiB
TypeScript
280 lines
10 KiB
TypeScript
import { render, screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { http, HttpResponse } from "msw";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { server } from "../test/msw";
|
|
import { workspaceSummaryFixture } from "../test/workspace-fixtures";
|
|
import { FakeEventSource } from "../test/fakeEventSource";
|
|
import { useSessionStore } from "../store/sessionStore";
|
|
import { clearAuthState, setAuthState } from "../auth/authState";
|
|
import { AppShell, resolveDatabaseManagementPresentation } from "./AppShell";
|
|
|
|
function renderShell() {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<AppShell canLogout={false} />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
async function expandAdministration() {
|
|
await userEvent.click(screen.getByRole("button", { name: "Administration" }));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
clearAuthState();
|
|
setAuthState({
|
|
issuer: "test",
|
|
subject: "test",
|
|
roles: ["admin"],
|
|
permissions: ["session.use", "workspace.manage", "workspace.secrets.manage", "database.manage", "pi.manage"],
|
|
isAdmin: true,
|
|
csrfToken: null,
|
|
session: null,
|
|
});
|
|
localStorage.clear();
|
|
window.history.replaceState(null, "", "/");
|
|
FakeEventSource.instances = [];
|
|
(globalThis as any).EventSource = FakeEventSource;
|
|
useSessionStore.getState().resetSession();
|
|
server.use(
|
|
http.get("/api/me", () => HttpResponse.json({
|
|
issuer: "test",
|
|
subject: "test",
|
|
displayName: "Test",
|
|
isAdmin: true,
|
|
})),
|
|
http.get("/api/sessions", () => HttpResponse.json([])),
|
|
http.get("/api/settings", () => HttpResponse.json({
|
|
workspace: "default",
|
|
provider: "test",
|
|
model: "test",
|
|
thinking: "low",
|
|
})),
|
|
http.get("/api/workspaces", () => HttpResponse.json([workspaceSummaryFixture("default")])),
|
|
http.get("/api/models", () => HttpResponse.json({ models: [{ provider: "test", id: "test", name: "Test LLM", reasoning: true }] })),
|
|
http.post("/api/runtime/prewarm", () => new HttpResponse(null, { status: 202 })),
|
|
http.get("/api/catalog/metrics", () => HttpResponse.json({
|
|
scope: "global",
|
|
databaseId: null,
|
|
tables: 0,
|
|
columns: 0,
|
|
sensitiveColumns: 0,
|
|
relationships: 0,
|
|
descriptionTargets: 0,
|
|
describedTargets: 0,
|
|
descriptionCoverage: 0,
|
|
updatedAt: null,
|
|
})),
|
|
);
|
|
});
|
|
|
|
test("uses Fleet Ledger by default and gates the legacy fallback to non-production environments", () => {
|
|
expect(resolveDatabaseManagementPresentation({
|
|
isDevelopment: true,
|
|
mode: "development",
|
|
search: "",
|
|
})).toBe("fleet");
|
|
expect(resolveDatabaseManagementPresentation({
|
|
isDevelopment: true,
|
|
mode: "development",
|
|
search: "?db-ui=legacy",
|
|
})).toBe("legacy");
|
|
expect(resolveDatabaseManagementPresentation({
|
|
isDevelopment: false,
|
|
mode: "staging",
|
|
legacyFlag: "true",
|
|
search: "",
|
|
})).toBe("legacy");
|
|
expect(resolveDatabaseManagementPresentation({
|
|
isDevelopment: false,
|
|
mode: "production",
|
|
legacyFlag: "true",
|
|
search: "?db-ui=legacy",
|
|
})).toBe("fleet");
|
|
});
|
|
|
|
test("starts on the core activity after a hard refresh even if metadata management was open", async () => {
|
|
localStorage.setItem("thothii:app-navigation", JSON.stringify({
|
|
surface: "database-management",
|
|
panel: null,
|
|
}));
|
|
|
|
renderShell();
|
|
await expandAdministration();
|
|
|
|
const sessionNavigation = screen.getByRole("complementary", { name: "Session navigation" });
|
|
expect(within(sessionNavigation).getByRole("button", { name: "Session" }))
|
|
.toHaveAttribute("aria-current", "page");
|
|
expect(within(sessionNavigation).getByRole("button", { name: "Database" }))
|
|
.not.toHaveAttribute("aria-current");
|
|
expect(screen.queryByRole("main", { name: "Database management" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("replaces the core conversation while keeping the session navigation", async () => {
|
|
renderShell();
|
|
await expandAdministration();
|
|
|
|
const conversationColumn = screen.getByTestId("conversation-column");
|
|
const sessionNavigation = screen.getByRole("complementary", { name: "Session navigation" });
|
|
const newSession = within(sessionNavigation).getByRole("button", { name: "Session" });
|
|
const databaseManagement = within(sessionNavigation).getByRole("button", { name: "Database" });
|
|
|
|
expect(newSession).toHaveAttribute("aria-current", "page");
|
|
expect(newSession).toHaveAttribute("data-navigation-state", "current");
|
|
expect(newSession).toHaveClass("bg-[oklch(var(--nav-active))]");
|
|
expect(databaseManagement).toHaveAttribute("data-navigation-state", "available");
|
|
expect(databaseManagement).not.toHaveAttribute("aria-current");
|
|
|
|
await userEvent.click(databaseManagement);
|
|
|
|
const manager = screen.getByRole("main", { name: "Database management" });
|
|
expect(manager).toBeVisible();
|
|
expect(conversationColumn).toContainElement(manager);
|
|
expect(screen.getByRole("complementary", { name: "Session navigation" })).toBe(sessionNavigation);
|
|
expect(sessionNavigation).toBeVisible();
|
|
expect(newSession).toBeVisible();
|
|
expect(newSession).toHaveAttribute("data-navigation-state", "available");
|
|
expect(newSession).not.toHaveAttribute("aria-current");
|
|
expect(newSession).not.toHaveClass("bg-[oklch(var(--nav-active))]");
|
|
expect(databaseManagement).toHaveAttribute("aria-current", "page");
|
|
expect(databaseManagement).toHaveAttribute("data-navigation-state", "current");
|
|
expect(databaseManagement).toHaveClass("bg-[oklch(var(--nav-active))]");
|
|
expect(screen.queryByRole("button", { name: "Back to workspace" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("textbox", { name: /new question/i })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("keeps a live core session connected while returning from database management", async () => {
|
|
let resumes = 0;
|
|
server.use(
|
|
http.get("/api/sessions", () => HttpResponse.json([{
|
|
id: "s1",
|
|
status: "open",
|
|
question: "Active question",
|
|
summary: null,
|
|
created_at: "2026-01-02T00:00:00Z",
|
|
updated_at: null,
|
|
author: "test",
|
|
name: null,
|
|
group: null,
|
|
archived: false,
|
|
active: true,
|
|
}])),
|
|
http.post("/api/sessions/:id/resume", ({ params }) => { resumes++; return HttpResponse.json({
|
|
id: params.id,
|
|
alreadyActive: true,
|
|
}); }),
|
|
http.get("/api/sessions/:id", ({ params }) => HttpResponse.json({
|
|
id: params.id,
|
|
status: "open",
|
|
phase: 1,
|
|
})),
|
|
);
|
|
renderShell();
|
|
await expandAdministration();
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Active sessions" }));
|
|
const session = await screen.findByTestId("session-item-s1");
|
|
await userEvent.click(session);
|
|
await waitFor(() => expect(FakeEventSource.instances).toHaveLength(1));
|
|
const source = FakeEventSource.instances[0];
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Database" }));
|
|
|
|
expect(screen.getByRole("main", { name: "Database management" })).toBeVisible();
|
|
expect(source.closed).toBe(false);
|
|
expect(FakeEventSource.instances).toHaveLength(1);
|
|
|
|
expect(session).toBeVisible();
|
|
expect(screen.queryByRole("button", { name: "Return to session" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "New session" })).not.toBeInTheDocument();
|
|
await userEvent.click(screen.getByRole("button", { name: "Session" }));
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByRole("main", { name: "Database management" })).not.toBeInTheDocument();
|
|
});
|
|
expect(source.closed).toBe(false);
|
|
expect(FakeEventSource.instances).toHaveLength(1);
|
|
expect(resumes).toBe(1);
|
|
await userEvent.click(screen.getByRole("button", { name: "Session" }));
|
|
expect(source.closed).toBe(false);
|
|
expect(resumes).toBe(1);
|
|
});
|
|
|
|
test("hides the complete administrative navigation from a regular user", () => {
|
|
clearAuthState();
|
|
setAuthState({
|
|
issuer: "test",
|
|
subject: "test",
|
|
roles: ["user"],
|
|
permissions: ["session.use"],
|
|
isAdmin: false,
|
|
csrfToken: null,
|
|
session: null,
|
|
});
|
|
|
|
renderShell();
|
|
|
|
expect(screen.getByRole("button", { name: "Session" })).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Administration" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Workspace" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Database" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Pi configuration" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("requires saving or cancelling a dirty database form before leaving", async () => {
|
|
server.use(http.get("/api/catalog/databases", () => HttpResponse.json([{
|
|
id: "11111111-1111-4111-8111-111111111111",
|
|
workspaceId: "default",
|
|
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: {
|
|
password: false,
|
|
apiKey: false,
|
|
sshPrivateKey: false,
|
|
sshPrivateKeyPassphrase: false,
|
|
sshKnownHosts: false,
|
|
tlsCa: false,
|
|
},
|
|
}])));
|
|
const confirm = vi.spyOn(window, "confirm").mockReturnValue(false);
|
|
renderShell();
|
|
await expandAdministration();
|
|
|
|
await userEvent.click(screen.getByRole("button", { name: "Database" }));
|
|
await userEvent.click(await screen.findByRole("button", { name: "Edit Policlinico San Donato" }));
|
|
const schema = await screen.findByLabelText("Schema");
|
|
await userEvent.clear(schema);
|
|
await userEvent.type(schema, "reporting");
|
|
|
|
const newSession = screen.getByRole("button", { name: "Session" });
|
|
await userEvent.click(newSession);
|
|
|
|
expect(confirm).not.toHaveBeenCalled();
|
|
expect(screen.getByRole("main", { name: "Database management" })).toBeVisible();
|
|
expect(schema).toHaveValue("reporting");
|
|
|
|
confirm.mockReturnValue(true);
|
|
await userEvent.click(screen.getByRole("button", { name: "Back to list" }));
|
|
await userEvent.click(newSession);
|
|
await waitFor(() => expect(screen.queryByRole("main", { name: "Database management" })).not.toBeInTheDocument());
|
|
confirm.mockRestore();
|
|
});
|