244 lines
8.4 KiB
TypeScript
244 lines
8.4 KiB
TypeScript
import { expect, test } from "vitest";
|
|
import { http, HttpResponse } from "msw";
|
|
import { server } from "../test/msw";
|
|
import { canonicalWorkspaceFixture, workspaceRevisionFixture, workspaceSummaryFixture } from "../test/workspace-fixtures";
|
|
import {
|
|
forgetWorkspaceSecret,
|
|
getWorkspace,
|
|
getWorkspaceRuntimeConfiguration,
|
|
listWorkspaces,
|
|
saveWorkspaceSecrets,
|
|
testWorkspace,
|
|
validateWorkspace,
|
|
} from "./workspaces";
|
|
|
|
const workspace = canonicalWorkspaceFixture("psd-clinical");
|
|
const revision = workspaceRevisionFixture("psd-clinical");
|
|
|
|
const runtimeConfiguration = {
|
|
workspaceId: "psd-clinical",
|
|
revision,
|
|
configurationState: "configuration_required",
|
|
requirements: [{
|
|
id: "dwh.password",
|
|
connector: "dwh",
|
|
label: "Data warehouse password",
|
|
description: "Password used by the selected data warehouse connection.",
|
|
input: "password",
|
|
required: true,
|
|
configured: false,
|
|
}],
|
|
} as const;
|
|
|
|
test("decodes read-only workspace summaries with a revision in every readiness state", async () => {
|
|
const ready = workspaceSummaryFixture("psd-clinical", {
|
|
displayName: "PSD Clinical",
|
|
revision,
|
|
});
|
|
const needsSecrets = {
|
|
...ready,
|
|
configurationState: "configuration_required" as const,
|
|
};
|
|
server.use(http.get("/api/workspaces", () => HttpResponse.json([ready, needsSecrets])));
|
|
|
|
await expect(listWorkspaces()).resolves.toEqual([ready, needsSecrets]);
|
|
});
|
|
|
|
test("accepts the immutable workspace revision contract", async () => {
|
|
server.use(http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({ workspace, revision })));
|
|
|
|
await expect(getWorkspace("psd-clinical")).resolves.toEqual({ workspace, revision });
|
|
});
|
|
|
|
test("accepts the evidence schema version materialized by the backend", async () => {
|
|
const persistedWorkspace = {
|
|
...workspace,
|
|
evidence: {
|
|
schema_version: 1,
|
|
source: {
|
|
type: "filesystem",
|
|
uri: "psd-clinical/evidence",
|
|
patterns: ["**/*.md"],
|
|
max_bytes: 10 * 1024 * 1024,
|
|
},
|
|
policy: { max_chunk_chars: 5000, retain_published_generations: 3 },
|
|
},
|
|
};
|
|
server.use(http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
|
|
workspace: persistedWorkspace,
|
|
revision,
|
|
})));
|
|
|
|
await expect(getWorkspace("psd-clinical")).resolves.toEqual({
|
|
workspace: persistedWorkspace,
|
|
revision,
|
|
});
|
|
});
|
|
|
|
test("validates a workspace read from the backend without losing its Evidence schema version", async () => {
|
|
const persistedWorkspace = {
|
|
...workspace,
|
|
evidence: {
|
|
schema_version: 1,
|
|
source: {
|
|
type: "filesystem",
|
|
uri: "psd-clinical/evidence",
|
|
patterns: ["curated/**/*.md"],
|
|
max_bytes: 10 * 1024 * 1024,
|
|
},
|
|
policy: { max_chunk_chars: 5000, retain_published_generations: 3 },
|
|
},
|
|
};
|
|
let validationRequest: unknown;
|
|
server.use(
|
|
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
|
|
workspace: persistedWorkspace,
|
|
revision,
|
|
})),
|
|
http.post("/api/workspaces/validate", async ({ request }) => {
|
|
validationRequest = await request.json();
|
|
return HttpResponse.json({
|
|
workspace: persistedWorkspace,
|
|
contract: {},
|
|
activatable: true,
|
|
diagnostics: [],
|
|
authentication: {
|
|
ready: true,
|
|
mode: "local",
|
|
checks: [{ level: "info", code: "auth_ready", message: "Authentication is ready." }],
|
|
},
|
|
});
|
|
}),
|
|
);
|
|
|
|
const loaded = await getWorkspace("psd-clinical");
|
|
await expect(validateWorkspace(loaded.workspace)).resolves.toMatchObject({ activatable: true });
|
|
expect(validationRequest).toEqual({ workspace: persistedWorkspace });
|
|
});
|
|
|
|
test("decodes runtime requirements but rejects any secret value returned by the server", async () => {
|
|
server.use(http.get(
|
|
"/api/workspaces/psd-clinical/runtime-configuration",
|
|
() => HttpResponse.json(runtimeConfiguration),
|
|
));
|
|
await expect(getWorkspaceRuntimeConfiguration("psd-clinical"))
|
|
.resolves.toEqual(runtimeConfiguration);
|
|
|
|
server.use(http.get(
|
|
"/api/workspaces/psd-clinical/runtime-configuration",
|
|
() => HttpResponse.json({
|
|
...runtimeConfiguration,
|
|
requirements: [{ ...runtimeConfiguration.requirements[0], value: "leaked-secret" }],
|
|
}),
|
|
));
|
|
await expect(getWorkspaceRuntimeConfiguration("psd-clinical"))
|
|
.rejects.toThrow("invalid runtime configuration");
|
|
});
|
|
|
|
test("blind secret replacement sends values once and returns status only", async () => {
|
|
let requestBody: unknown;
|
|
server.use(http.put("/api/workspaces/psd-clinical/secrets", async ({ request }) => {
|
|
requestBody = await request.json();
|
|
return HttpResponse.json({
|
|
...runtimeConfiguration,
|
|
configurationState: "ready",
|
|
requirements: [{ ...runtimeConfiguration.requirements[0], configured: true }],
|
|
});
|
|
}));
|
|
|
|
const response = await saveWorkspaceSecrets("psd-clinical", {
|
|
"dwh.password": "one-time-value",
|
|
});
|
|
|
|
expect(requestBody).toEqual({ values: { "dwh.password": "one-time-value" } });
|
|
expect(response.configurationState).toBe("ready");
|
|
expect(JSON.stringify(response)).not.toContain("one-time-value");
|
|
});
|
|
|
|
test("forget targets one declared requirement", async () => {
|
|
let called = false;
|
|
server.use(http.delete("/api/workspaces/psd-clinical/secrets/dwh.password", () => {
|
|
called = true;
|
|
return HttpResponse.json(runtimeConfiguration);
|
|
}));
|
|
|
|
await expect(forgetWorkspaceSecret("psd-clinical", "dwh.password"))
|
|
.resolves.toEqual(runtimeConfiguration);
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
test("decodes the shared authentication diagnostics on static validation and live connection tests", async () => {
|
|
const authentication = {
|
|
ready: false,
|
|
mode: "oidc" as const,
|
|
checks: [{
|
|
level: "error" as const,
|
|
code: "oidc_mapped_group_missing" as const,
|
|
field: "Thoth Administrators",
|
|
message: "A configured authorization group does not exist.",
|
|
}],
|
|
};
|
|
server.use(
|
|
http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace,
|
|
contract: {},
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication,
|
|
})),
|
|
http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication,
|
|
})),
|
|
);
|
|
|
|
await expect(validateWorkspace(workspace)).resolves.toMatchObject({
|
|
activatable: false,
|
|
authentication,
|
|
});
|
|
await expect(testWorkspace("psd-clinical")).resolves.toMatchObject({
|
|
activatable: false,
|
|
authentication,
|
|
});
|
|
});
|
|
|
|
test.each([
|
|
["ready with error", { ready: true, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure" }] }],
|
|
["failed with ready", { ready: false, mode: "oidc", checks: [{ level: "info", code: "auth_ready", message: "ready" }] }],
|
|
["failed without error", { ready: false, mode: "oidc", checks: [{ level: "info", code: "auth_config_invalid", message: "info" }] }],
|
|
["duplicate", { ready: false, mode: "oidc", checks: [
|
|
{ level: "error", code: "oidc_secret_missing", message: "one" },
|
|
{ level: "error", code: "oidc_secret_missing", message: "two" },
|
|
] }],
|
|
["attacker field", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", field: "attacker-field-SENTINEL" }] }],
|
|
["control", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_mapped_group_missing", message: "failure", field: "bad\u0085field" }] }],
|
|
["unexpected property", { ready: false, mode: "oidc", checks: [{ level: "error", code: "oidc_secret_missing", message: "failure", attacker: "field" }] }],
|
|
])("rejects hostile authentication diagnostics: %s", async (_name, authentication) => {
|
|
server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace,
|
|
contract: {},
|
|
activatable: false,
|
|
diagnostics: [],
|
|
authentication,
|
|
})));
|
|
|
|
await expect(validateWorkspace(workspace)).rejects.toThrow("invalid authentication diagnostics");
|
|
});
|
|
|
|
test("rejects a workspace claimed activatable when authentication is not ready", async () => {
|
|
server.use(http.post("/api/workspaces/validate", () => HttpResponse.json({
|
|
workspace,
|
|
contract: {},
|
|
activatable: true,
|
|
diagnostics: [],
|
|
authentication: {
|
|
ready: false,
|
|
mode: "oidc",
|
|
checks: [{ level: "error", code: "oidc_secret_missing", message: "Authentication is unavailable." }],
|
|
},
|
|
})));
|
|
|
|
await expect(validateWorkspace(workspace)).rejects.toThrow("invalid diagnostic result");
|
|
});
|