1058 lines
46 KiB
TypeScript
1058 lines
46 KiB
TypeScript
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { act, render, screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { http, HttpResponse } from "msw";
|
|
import { beforeEach, expect, test, vi } from "vitest";
|
|
import { server } from "../test/msw";
|
|
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures";
|
|
import { WorkspaceManager } from "./WorkspaceManager";
|
|
import { setAuthState } from "../auth/authState";
|
|
import { queryClient } from "../app/queryClient";
|
|
|
|
const workspace = canonicalWorkspaceFixture("psd-clinical");
|
|
const revision = workspaceRevisionFixture("psd-clinical");
|
|
const authenticatedWorkspaceUser = {
|
|
issuer: "local", subject: "workspace-user", roles: ["user"] as const,
|
|
permissions: ["workspace.manage", "workspace.secrets.manage"], isAdmin: false,
|
|
csrfToken: "w".repeat(43), session: null,
|
|
};
|
|
const requirement = {
|
|
id: "dwh.password",
|
|
connector: "dwh",
|
|
label: "Data warehouse password",
|
|
description: "Password used by the selected data warehouse connection.",
|
|
input: "password",
|
|
required: true,
|
|
configured: false,
|
|
};
|
|
|
|
const readyAuthentication = {
|
|
ready: true,
|
|
mode: "none",
|
|
checks: [{ level: "info", code: "auth_ready", message: "Authentication is ready." }],
|
|
};
|
|
|
|
function runtimeConfiguration(configured = false) {
|
|
return {
|
|
workspaceId: "psd-clinical",
|
|
revision,
|
|
configurationState: configured ? "ready" : "configuration_required",
|
|
requirements: [{ ...requirement, configured }],
|
|
};
|
|
}
|
|
|
|
function renderManager(onClose = vi.fn()) {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return {
|
|
onClose,
|
|
...render(
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager open onClose={onClose} canManageWorkspace canManageSecrets />
|
|
</QueryClientProvider>,
|
|
),
|
|
};
|
|
}
|
|
|
|
function renderDeniedManager() {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
return render(
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager open onClose={vi.fn()} canManageWorkspace={false} canManageSecrets={false} />
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
type ManagerSettings = {
|
|
open: boolean;
|
|
canManageWorkspace: boolean;
|
|
canManageSecrets: boolean;
|
|
};
|
|
|
|
function renderManagerWithSettings(initial: Partial<ManagerSettings> = {}) {
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const onClose = vi.fn();
|
|
let settings: ManagerSettings = {
|
|
open: true,
|
|
canManageWorkspace: true,
|
|
canManageSecrets: true,
|
|
...initial,
|
|
};
|
|
const tree = () => (
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager onClose={onClose} {...settings} />
|
|
</QueryClientProvider>
|
|
);
|
|
const view = render(tree());
|
|
return {
|
|
...view,
|
|
client,
|
|
onClose,
|
|
rerender(next: Partial<ManagerSettings>) {
|
|
settings = { ...settings, ...next };
|
|
view.rerender(tree());
|
|
},
|
|
};
|
|
}
|
|
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
setAuthState(authenticatedWorkspaceUser);
|
|
server.use(
|
|
http.get("/api/workspace-registry/status", () => HttpResponse.json({
|
|
branch: "main",
|
|
head: "a".repeat(40),
|
|
ahead: 0,
|
|
behind: 0,
|
|
degraded: false,
|
|
repository: {
|
|
host: "git.example.test",
|
|
repository: "analytics/thoth-workspaces",
|
|
transport: "ssh",
|
|
},
|
|
})),
|
|
http.get("/api/workspaces", () => HttpResponse.json([
|
|
workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical",
|
|
description: "Clinical data",
|
|
configurationState: "configuration_required",
|
|
revision,
|
|
}),
|
|
])),
|
|
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace, revision })),
|
|
http.get("/api/workspaces/psd-clinical/runtime-configuration", () => (
|
|
HttpResponse.json(runtimeConfiguration())
|
|
)),
|
|
);
|
|
});
|
|
|
|
test("defaults workspace mutations and secrets to denied", async () => {
|
|
renderDeniedManager();
|
|
expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible();
|
|
expect(screen.queryByRole("button", { name: "Update workspace repository" })).not.toBeInTheDocument();
|
|
await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible();
|
|
expect(screen.queryByRole("button", { name: "Validate workspace source" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Test workspace connections" })).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("heading", { name: "Runtime secrets" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("uses the shared modeless work-area panel and keeps workspace content scrollable", () => {
|
|
renderManager();
|
|
|
|
const dialog = screen.getByRole("dialog", { name: "Workspace management" });
|
|
expect(dialog).toHaveAttribute("aria-modal", "false");
|
|
expect(dialog).toHaveAttribute("data-work-area-panel");
|
|
expect(dialog).toHaveAttribute("data-width", "expanded");
|
|
expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="header"]')).toHaveLength(1);
|
|
expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="body"]')).toHaveLength(1);
|
|
expect(dialog.querySelectorAll(':scope > [data-work-area-panel-region="footer"]')).toHaveLength(0);
|
|
expect(screen.getByRole("region", { name: "Workspace configuration" })).toHaveClass("overflow-y-auto");
|
|
});
|
|
|
|
test("offers a button above the workspace list that returns to Level 1", async () => {
|
|
const user = userEvent.setup();
|
|
const onClose = vi.fn();
|
|
renderManager(onClose);
|
|
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible();
|
|
|
|
const navigation = screen.getByRole("navigation", { name: "Workspaces" });
|
|
const backButton = within(navigation).getByRole("button", { name: "Back to Level 1" });
|
|
expect(backButton).toHaveClass("border-border", "bg-card", "w-full");
|
|
expect(backButton.compareDocumentPosition(within(navigation).getByText("Available workspaces"))
|
|
& Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
|
|
await user.click(backButton);
|
|
|
|
expect(onClose).not.toHaveBeenCalled();
|
|
expect(screen.getByTestId("workspace-overview")).toBeVisible();
|
|
expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("level one explains workspace files, shared repositories, and the read-only Git flow", async () => {
|
|
const user = userEvent.setup();
|
|
server.use(http.post("/api/workspace-registry/pull", () => HttpResponse.json({
|
|
branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false,
|
|
})));
|
|
renderManager();
|
|
|
|
expect(await screen.findByRole("heading", { name: "Workspace management" })).toBeVisible();
|
|
const overview = screen.getByTestId("workspace-overview");
|
|
expect(within(overview).getByRole("heading", { name: "What workspace files tell ThothII" })).toBeVisible();
|
|
expect(within(overview).getByText(/A workspace file describes one data environment/i)).toBeVisible();
|
|
expect(within(overview).getByText(/one Git repository can contain many workspaces/i)).toBeVisible();
|
|
expect(within(overview).getByText(/each workspace has its own directory/i)).not.toHaveTextContent(/database target/i);
|
|
expect(within(overview).getByText(/each workspace has its own directory/i)).toHaveTextContent(/Evidence/i);
|
|
const repositorySteps = within(overview).getByRole("list");
|
|
expect(repositorySteps).toHaveClass("gap-0");
|
|
expect(within(repositorySteps).getAllByRole("listitem")).toHaveLength(3);
|
|
expect(within(overview).getByRole("link", { name: /workspace authoring instructions on GitHub/i })).toHaveAttribute(
|
|
"href",
|
|
"https://github.com/mptyl/ThothII/blob/main/docs/install/local-workspace-registry.md#prepare-and-publish-a-workspace-source",
|
|
);
|
|
const repositoryLink = await within(overview).findByRole("link", {
|
|
name: "git.example.test/analytics/thoth-workspaces",
|
|
});
|
|
expect(repositoryLink).toHaveAttribute("href", "https://git.example.test/analytics/thoth-workspaces");
|
|
expect(repositoryLink).toHaveAttribute("target", "_blank");
|
|
expect(repositoryLink).toHaveAttribute("rel", "noreferrer");
|
|
expect(within(overview).getByText(/configured during ThothII installation/i)).toBeVisible();
|
|
expect(within(overview).getAllByText(/managed read-only checkout/i)).toHaveLength(2);
|
|
expect(within(overview).getByText(/current active revision remains unchanged/i)).toBeVisible();
|
|
expect(within(overview).getByText(/No workspace selection is required/i)).toBeVisible();
|
|
|
|
await user.click(screen.getByRole("button", { name: "Update workspace repository" }));
|
|
expect(await screen.findByText("Workspace repository updated and validated.")).toBeVisible();
|
|
expect(screen.getByText(/A workspace file describes one data environment/i)).toBeInTheDocument();
|
|
expect(screen.queryByText(/create a workspace repository/i)).not.toBeInTheDocument();
|
|
expect(screen.queryByText(/import|export|bundle/i)).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("shows a local workspace repository as text instead of an invalid web link", async () => {
|
|
server.use(http.get("/api/workspace-registry/status", () => HttpResponse.json({
|
|
branch: "main",
|
|
head: "a".repeat(40),
|
|
ahead: 0,
|
|
behind: 0,
|
|
degraded: false,
|
|
repository: {
|
|
host: "local",
|
|
repository: "configured-repository",
|
|
transport: "local",
|
|
},
|
|
})));
|
|
renderManager();
|
|
|
|
const overview = await screen.findByTestId("workspace-overview");
|
|
expect(await within(overview).findByText("local/configured-repository")).toBeVisible();
|
|
expect(within(overview).queryByRole("link", { name: "local/configured-repository" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("workspace-specific commands remain isolated until a workspace is selected", async () => {
|
|
const user = userEvent.setup();
|
|
renderManager();
|
|
|
|
expect(screen.queryByRole("heading", { name: "Workspace-specific actions" })).not.toBeInTheDocument();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
expect(await screen.findByRole("heading", { name: "About this workspace" })).toBeVisible();
|
|
expect(await screen.findByRole("heading", { name: "Workspace-specific actions" })).toBeVisible();
|
|
expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/workspace.yaml/i);
|
|
expect(screen.getByText(/one workspace in the shared repository/i)).toHaveTextContent(/Evidence/i);
|
|
expect(screen.getByText(/exact version currently activated by ThothII/i)).toBeVisible();
|
|
expect(screen.getByText(/does not modify the repository/i)).toBeVisible();
|
|
expect(screen.getByText(/confirms that workspace.yaml and the directories/i)).toBeVisible();
|
|
expect(screen.getByText(/database configured in Database Management/i)).toBeVisible();
|
|
expect(screen.getByText(/Evidence source and the installation semantic services/i)).toBeVisible();
|
|
expect(screen.getByText(/result is informational/i)).toBeVisible();
|
|
const languageField = screen.getByText("Workspace language").parentElement;
|
|
expect(languageField).not.toBeNull();
|
|
expect(languageField).toHaveTextContent("en");
|
|
expect(screen.getByRole("button", { name: "Validate workspace source" })).toBeVisible();
|
|
expect(screen.getByRole("button", { name: "Test workspace connections" })).toBeVisible();
|
|
});
|
|
|
|
test("keeps validation and connection results inside their respective action cards", async () => {
|
|
const user = userEvent.setup();
|
|
server.use(
|
|
http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
})),
|
|
http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({
|
|
activatable: false,
|
|
diagnostics: [{ level: "error", code: "connector_unavailable", message: "Connector diagnostic failed." }],
|
|
authentication: readyAuthentication,
|
|
})),
|
|
);
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
const validationCard = screen.getByTestId("workspace-validation-card");
|
|
const connectionCard = screen.getByTestId("workspace-connection-card");
|
|
await user.click(within(validationCard).getByRole("button", { name: "Validate workspace source" }));
|
|
const validationStatus = await within(validationCard).findByRole("status");
|
|
expect(validationStatus).toHaveTextContent("Workspace source and authentication are valid.");
|
|
expect(validationStatus).toHaveClass("text-emerald-700");
|
|
expect(within(connectionCard).queryByText("Workspace source and authentication are valid.")).not.toBeInTheDocument();
|
|
|
|
await user.click(within(connectionCard).getByRole("button", { name: "Test workspace connections" }));
|
|
expect(await within(connectionCard).findByRole("alert")).toHaveTextContent(
|
|
"connector_unavailable: Connector diagnostic failed.",
|
|
);
|
|
expect(within(validationCard).queryByText("connector_unavailable: Connector diagnostic failed.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("renders one authentication section with configured-group errors and no unmapped-group list", async () => {
|
|
const user = userEvent.setup();
|
|
server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace,
|
|
contract: {},
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication: {
|
|
ready: false,
|
|
mode: "oidc",
|
|
checks: [{
|
|
level: "error",
|
|
code: "oidc_mapped_group_missing",
|
|
field: "Thoth Administrators",
|
|
message: "A configured authorization group does not exist.",
|
|
}],
|
|
},
|
|
})));
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
|
|
const section = await screen.findByTestId("workspace-authentication");
|
|
expect(within(section).getByRole("heading", { name: "Authentication" })).toBeVisible();
|
|
expect(within(section).getByText("Failed")).toBeVisible();
|
|
expect(within(section).getByText("oidc_mapped_group_missing: Thoth Administrators — A configured authorization group does not exist.")).toBeVisible();
|
|
expect(within(section).queryByText(/unmapped/i)).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("clears a previous authentication result as soon as validation is retried", async () => {
|
|
const user = userEvent.setup();
|
|
let calls = 0;
|
|
let releaseRetry!: () => void;
|
|
const retryStarted = new Promise<void>((resolve) => { releaseRetry = resolve; });
|
|
server.use(http.post("/api/workspaces/validate", async () => {
|
|
calls += 1;
|
|
if (calls > 1) await retryStarted;
|
|
return HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
});
|
|
}));
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
const button = screen.getByRole("button", { name: "Validate workspace source" });
|
|
|
|
await user.click(button);
|
|
expect(await screen.findByTestId("workspace-authentication")).toBeVisible();
|
|
|
|
await user.click(button);
|
|
await waitFor(() => expect(calls).toBe(2));
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
|
|
releaseRetry();
|
|
expect(await screen.findByTestId("workspace-authentication")).toBeVisible();
|
|
});
|
|
|
|
test("ignores an older validation response that completes after a newer connection test", async () => {
|
|
const user = userEvent.setup();
|
|
let releaseValidation!: () => void;
|
|
let releaseTest!: () => void;
|
|
let validationStarted!: () => void;
|
|
let testStarted!: () => void;
|
|
let validationSettled!: () => void;
|
|
const heldValidation = new Promise<void>((resolve) => { releaseValidation = resolve; });
|
|
const heldTest = new Promise<void>((resolve) => { releaseTest = resolve; });
|
|
const validationRequestStarted = new Promise<void>((resolve) => { validationStarted = resolve; });
|
|
const testRequestStarted = new Promise<void>((resolve) => { testStarted = resolve; });
|
|
const validationRequestSettled = new Promise<void>((resolve) => { validationSettled = resolve; });
|
|
server.use(
|
|
http.post("/api/workspaces/validate", async () => {
|
|
validationStarted();
|
|
try {
|
|
await heldValidation;
|
|
return HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
});
|
|
} finally {
|
|
validationSettled();
|
|
}
|
|
}),
|
|
http.post("/api/workspaces/psd-clinical/test", async () => {
|
|
testStarted();
|
|
await heldTest;
|
|
return HttpResponse.json({
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication: {
|
|
ready: false,
|
|
mode: "oidc",
|
|
checks: [{ level: "error", code: "oidc_secret_missing", message: "Newer connection result." }],
|
|
},
|
|
});
|
|
}),
|
|
);
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
await validationRequestStarted;
|
|
await user.click(screen.getByRole("button", { name: "Test workspace connections" }));
|
|
await testRequestStarted;
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
|
|
act(() => releaseTest());
|
|
const authentication = await screen.findByTestId("workspace-authentication");
|
|
expect(within(authentication).getByText(/Newer connection result/)).toBeVisible();
|
|
expect(screen.getByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
|
|
act(() => releaseValidation());
|
|
await act(async () => {
|
|
await validationRequestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
expect(within(authentication).getByText(/Newer connection result/)).toBeVisible();
|
|
expect(within(authentication).queryByText("Passed")).not.toBeInTheDocument();
|
|
expect(screen.getByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
});
|
|
|
|
test("does not apply a diagnostic that completes after its dialog is closed and reopened", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.post("/api/workspaces/validate", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
});
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
|
const onClose = vi.fn();
|
|
const view = render(
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager open onClose={onClose} canManageWorkspace canManageSecrets />
|
|
</QueryClientProvider>,
|
|
);
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Close workspace management" }));
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
view.rerender(
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager open={false} onClose={onClose} canManageWorkspace canManageSecrets />
|
|
</QueryClientProvider>,
|
|
);
|
|
view.rerender(
|
|
<QueryClientProvider client={client}>
|
|
<WorkspaceManager open onClose={onClose} canManageWorkspace canManageSecrets />
|
|
</QueryClientProvider>,
|
|
);
|
|
expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("does not apply a diagnostic that completes after returning through Level 1", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.post("/api/workspaces/validate", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
});
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
await requestStarted;
|
|
|
|
await user.click(within(screen.getByRole("navigation", { name: "Workspaces" }))
|
|
.getByRole("button", { name: "Back to Level 1" }));
|
|
expect(screen.getByTestId("workspace-overview")).toBeVisible();
|
|
await user.click(screen.getByRole("button", { name: "PSD Clinical" }));
|
|
expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("does not apply a diagnostic after changing workspaces and returning", async () => {
|
|
const user = userEvent.setup();
|
|
const alternateId = "other-clinical";
|
|
const alternateWorkspace = canonicalWorkspaceFixture(alternateId);
|
|
const alternateRevision = workspaceRevisionFixture(alternateId);
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(
|
|
http.get("/api/workspaces", () => HttpResponse.json([
|
|
workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical",
|
|
description: "Clinical data",
|
|
configurationState: "configuration_required",
|
|
revision,
|
|
}),
|
|
workspaceSummaryFixture(alternateId, {
|
|
displayName: "Other Clinical",
|
|
description: "Other data",
|
|
configurationState: "configuration_required",
|
|
revision: alternateRevision,
|
|
}),
|
|
])),
|
|
http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({
|
|
workspace: alternateWorkspace,
|
|
revision: alternateRevision,
|
|
})),
|
|
http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({
|
|
workspaceId: alternateId,
|
|
revision: alternateRevision,
|
|
configurationState: "configuration_required",
|
|
requirements: [{ ...requirement }],
|
|
})),
|
|
http.post("/api/workspaces/validate", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({
|
|
workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication,
|
|
});
|
|
} finally {
|
|
settled();
|
|
}
|
|
}),
|
|
);
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Other Clinical" }));
|
|
expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible();
|
|
await user.click(screen.getByRole("button", { name: "PSD Clinical" }));
|
|
expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("never renders a hostile authentication field rejected by the API decoder", async () => {
|
|
const user = userEvent.setup();
|
|
const attacker = "attacker-field-SENTINEL";
|
|
server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace,
|
|
contract: {},
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication: {
|
|
ready: false,
|
|
mode: "oidc",
|
|
checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", field: attacker }],
|
|
},
|
|
})));
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
|
|
expect(await screen.findByRole("alert")).toBeVisible();
|
|
expect(screen.queryByText(new RegExp(attacker))).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("renders binding_ok as a green connection success", async () => {
|
|
const user = userEvent.setup();
|
|
server.use(
|
|
http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({
|
|
activatable: true,
|
|
diagnostics: [{ level: "info", code: "binding_ok", message: "Installation bindings and diagnostics succeeded." }],
|
|
authentication: readyAuthentication,
|
|
})),
|
|
);
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
const connectionCard = screen.getByTestId("workspace-connection-card");
|
|
await user.click(within(connectionCard).getByRole("button", { name: "Test workspace connections" }));
|
|
const connectionStatus = await within(connectionCard).findByRole("status");
|
|
expect(connectionStatus).toHaveTextContent("binding_ok: Installation bindings and diagnostics succeeded.");
|
|
expect(connectionStatus).toHaveClass("text-emerald-700");
|
|
expect(within(connectionCard).queryByRole("alert")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("secret fields are write-only, clear after blind save, and may be forgotten", async () => {
|
|
const user = userEvent.setup();
|
|
let savedBody: unknown;
|
|
server.use(
|
|
http.put("/api/workspaces/psd-clinical/secrets", async ({ request }) => {
|
|
savedBody = await request.json();
|
|
return HttpResponse.json(runtimeConfiguration(true));
|
|
}),
|
|
http.delete("/api/workspaces/psd-clinical/secrets/dwh.password", () => (
|
|
HttpResponse.json(runtimeConfiguration(false))
|
|
)),
|
|
);
|
|
renderManager();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
|
|
const input = await screen.findByLabelText("Data warehouse password");
|
|
expect(input).toHaveValue("");
|
|
expect(input).toHaveAttribute("type", "password");
|
|
expect(screen.getByText("Not configured")).toBeVisible();
|
|
await user.type(input, "one-time-password");
|
|
await user.click(screen.getByRole("button", { name: "Save entered secrets" }));
|
|
|
|
await waitFor(() => expect(savedBody).toEqual({
|
|
values: { "dwh.password": "one-time-password" },
|
|
}));
|
|
expect(input).toHaveValue("");
|
|
expect(await screen.findByText("Configured")).toBeVisible();
|
|
expect(screen.queryByDisplayValue("one-time-password")).not.toBeInTheDocument();
|
|
expect(localStorage.length).toBe(0);
|
|
|
|
await user.click(screen.getByRole("button", { name: "Forget stored Data warehouse password" }));
|
|
expect(await screen.findByText("Not configured")).toBeVisible();
|
|
});
|
|
|
|
test("a delayed runtime-secret save from user A cannot repopulate user B's cache or notice", async () => {
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json(runtimeConfiguration(true));
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
queryClient.clear();
|
|
setAuthState({ issuer: "local", subject: "user-a", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "a".repeat(43), session: null });
|
|
render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<WorkspaceManager open onClose={vi.fn()} canManageWorkspace canManageSecrets />
|
|
</QueryClientProvider>,
|
|
);
|
|
await userEvent.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
const input = await screen.findByLabelText("Data warehouse password");
|
|
await userEvent.type(input, "a-secret");
|
|
await userEvent.click(screen.getByRole("button", { name: "Save entered secrets" }));
|
|
await requestStarted;
|
|
|
|
act(() => setAuthState({ issuer: "local", subject: "user-b", roles: ["user"], permissions: ["workspace.secrets.manage"], isAdmin: false, csrfToken: "b".repeat(43), session: null }));
|
|
expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined();
|
|
release();
|
|
await act(async () => { await requestSettled; });
|
|
|
|
expect(queryClient.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toBeUndefined();
|
|
expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred secret save cannot update a reopened dialog", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json(runtimeConfiguration(true));
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password");
|
|
await user.click(screen.getByRole("button", { name: "Save entered secrets" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Close workspace management" }));
|
|
manager.rerender({ open: false });
|
|
manager.rerender({ open: true });
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret");
|
|
expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled();
|
|
const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]);
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease);
|
|
expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred secret save cannot update a workspace selected again after a cross-workspace transition", async () => {
|
|
const user = userEvent.setup();
|
|
const alternateId = "other-clinical";
|
|
const alternateWorkspace = canonicalWorkspaceFixture(alternateId);
|
|
const alternateRevision = workspaceRevisionFixture(alternateId);
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(
|
|
http.get("/api/workspaces", () => HttpResponse.json([
|
|
workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision,
|
|
}),
|
|
workspaceSummaryFixture(alternateId, {
|
|
displayName: "Other Clinical", description: "Other data", configurationState: "configuration_required", revision: alternateRevision,
|
|
}),
|
|
])),
|
|
http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({ workspace: alternateWorkspace, revision: alternateRevision })),
|
|
http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({
|
|
workspaceId: alternateId, revision: alternateRevision, configurationState: "configuration_required", requirements: [{ ...requirement }],
|
|
})),
|
|
http.put("/api/workspaces/psd-clinical/secrets", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json(runtimeConfiguration(true));
|
|
} finally {
|
|
settled();
|
|
}
|
|
}),
|
|
);
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password");
|
|
await user.click(screen.getByRole("button", { name: "Save entered secrets" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Other Clinical" }));
|
|
expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible();
|
|
await user.click(screen.getByRole("button", { name: "PSD Clinical" }));
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret");
|
|
expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled();
|
|
const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]);
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease);
|
|
expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred secret save cannot update a context after its secret permission changes", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.put("/api/workspaces/psd-clinical/secrets", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json(runtimeConfiguration(true));
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "one-time-password");
|
|
await user.click(screen.getByRole("button", { name: "Save entered secrets" }));
|
|
await requestStarted;
|
|
|
|
manager.rerender({ canManageSecrets: false });
|
|
manager.rerender({ canManageSecrets: true });
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "new-secret");
|
|
expect(await screen.findByRole("button", { name: "Save entered secrets" })).not.toBeDisabled();
|
|
const cacheBeforeRelease = manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"]);
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect(manager.client.getQueryData(["workspace-runtime-configuration", "psd-clinical"])).toEqual(cacheBeforeRelease);
|
|
expect(screen.queryByText("Runtime secrets saved. Stored values remain hidden.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred repository update cannot update a reopened dialog or refetch its cache", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
let statusRequests = 0;
|
|
let workspaceRequests = 0;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(
|
|
http.get("/api/workspace-registry/status", () => {
|
|
statusRequests++;
|
|
return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
}),
|
|
http.get("/api/workspaces", () => {
|
|
workspaceRequests++;
|
|
return HttpResponse.json([workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision,
|
|
})]);
|
|
}),
|
|
http.post("/api/workspace-registry/pull", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
} finally {
|
|
settled();
|
|
}
|
|
}),
|
|
);
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "Update workspace repository" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Close workspace management" }));
|
|
manager.rerender({ open: false });
|
|
manager.rerender({ open: true });
|
|
expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled();
|
|
const requestsBeforeRelease = { statusRequests, workspaceRequests };
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease);
|
|
expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred repository update cannot update a context after a cross-workspace transition", async () => {
|
|
const user = userEvent.setup();
|
|
const alternateId = "other-clinical";
|
|
const alternateWorkspace = canonicalWorkspaceFixture(alternateId);
|
|
const alternateRevision = workspaceRevisionFixture(alternateId);
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
let statusRequests = 0;
|
|
let workspaceRequests = 0;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(
|
|
http.get("/api/workspace-registry/status", () => {
|
|
statusRequests++;
|
|
return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
}),
|
|
http.get("/api/workspaces", () => {
|
|
workspaceRequests++;
|
|
return HttpResponse.json([
|
|
workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision,
|
|
}),
|
|
workspaceSummaryFixture(alternateId, {
|
|
displayName: "Other Clinical", description: "Other data", configurationState: "configuration_required", revision: alternateRevision,
|
|
}),
|
|
]);
|
|
}),
|
|
http.get(`/api/workspaces/${alternateId}`, () => HttpResponse.json({ workspace: alternateWorkspace, revision: alternateRevision })),
|
|
http.get(`/api/workspaces/${alternateId}/runtime-configuration`, () => HttpResponse.json({
|
|
workspaceId: alternateId, revision: alternateRevision, configurationState: "configuration_required", requirements: [{ ...requirement }],
|
|
})),
|
|
http.post("/api/workspace-registry/pull", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
} finally {
|
|
settled();
|
|
}
|
|
}),
|
|
);
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "Update workspace repository" }));
|
|
await requestStarted;
|
|
|
|
await user.click(screen.getByRole("button", { name: "Other Clinical" }));
|
|
expect(await screen.findByRole("heading", { name: "Other Clinical" })).toBeVisible();
|
|
await user.click(within(screen.getByRole("navigation", { name: "Workspaces" }))
|
|
.getByRole("button", { name: "Back to Level 1" }));
|
|
expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled();
|
|
const requestsBeforeRelease = { statusRequests, workspaceRequests };
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease);
|
|
expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred repository update cannot update a context after workspace permission changes", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
let statusRequests = 0;
|
|
let workspaceRequests = 0;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(
|
|
http.get("/api/workspace-registry/status", () => {
|
|
statusRequests++;
|
|
return HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
}),
|
|
http.get("/api/workspaces", () => {
|
|
workspaceRequests++;
|
|
return HttpResponse.json([workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical", description: "Clinical data", configurationState: "configuration_required", revision,
|
|
})]);
|
|
}),
|
|
http.post("/api/workspace-registry/pull", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({ branch: "main", head: "b".repeat(40), ahead: 0, behind: 0, degraded: false });
|
|
} finally {
|
|
settled();
|
|
}
|
|
}),
|
|
);
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "Update workspace repository" }));
|
|
await requestStarted;
|
|
|
|
manager.rerender({ canManageWorkspace: false });
|
|
manager.rerender({ canManageWorkspace: true });
|
|
expect(await screen.findByRole("button", { name: "Update workspace repository" })).not.toBeDisabled();
|
|
const requestsBeforeRelease = { statusRequests, workspaceRequests };
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect({ statusRequests, workspaceRequests }).toEqual(requestsBeforeRelease);
|
|
expect(screen.queryByText("Workspace repository updated and validated.")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred validation cannot update a context after workspace permission changes", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.post("/api/workspaces/validate", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({ workspace, contract: {}, activatable: true, diagnostics: [], authentication: readyAuthentication });
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.click(screen.getByRole("button", { name: "Validate workspace source" }));
|
|
await requestStarted;
|
|
|
|
manager.rerender({ canManageWorkspace: false });
|
|
manager.rerender({ canManageWorkspace: true });
|
|
expect(await screen.findByRole("button", { name: "Validate workspace source" })).not.toBeDisabled();
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("a deferred connection test cannot update a context after workspace permission changes", async () => {
|
|
const user = userEvent.setup();
|
|
let release!: () => void;
|
|
let started!: () => void;
|
|
let settled!: () => void;
|
|
const held = new Promise<void>((resolve) => { release = resolve; });
|
|
const requestStarted = new Promise<void>((resolve) => { started = resolve; });
|
|
const requestSettled = new Promise<void>((resolve) => { settled = resolve; });
|
|
server.use(http.post("/api/workspaces/psd-clinical/test", async () => {
|
|
started();
|
|
try {
|
|
await held;
|
|
return HttpResponse.json({ activatable: true, diagnostics: [], authentication: readyAuthentication });
|
|
} finally {
|
|
settled();
|
|
}
|
|
}));
|
|
const manager = renderManagerWithSettings();
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.click(screen.getByRole("button", { name: "Test workspace connections" }));
|
|
await requestStarted;
|
|
|
|
manager.rerender({ canManageWorkspace: false });
|
|
manager.rerender({ canManageWorkspace: true });
|
|
expect(await screen.findByRole("button", { name: "Test workspace connections" })).not.toBeDisabled();
|
|
|
|
act(() => release());
|
|
await act(async () => {
|
|
await requestSettled;
|
|
await new Promise((resolve) => { setTimeout(resolve, 50); });
|
|
});
|
|
|
|
expect(screen.queryByTestId("workspace-authentication")).not.toBeInTheDocument();
|
|
});
|
|
|
|
test("closing clears unsaved secret fields", async () => {
|
|
const user = userEvent.setup();
|
|
const onClose = vi.fn();
|
|
renderManager(onClose);
|
|
await user.click(await screen.findByRole("button", { name: "PSD Clinical" }));
|
|
await user.type(await screen.findByLabelText("Data warehouse password"), "unsaved-value");
|
|
|
|
await user.click(screen.getByRole("button", { name: "Close workspace management" }));
|
|
|
|
expect(onClose).toHaveBeenCalledTimes(1);
|
|
expect(screen.queryByDisplayValue("unsaved-value")).not.toBeInTheDocument();
|
|
});
|