fix: harden Pi management readiness

This commit is contained in:
2026-08-05 06:20:25 +02:00
parent 7a8bcacbfe
commit 7df21b5f21
24 changed files with 577 additions and 274 deletions
+18 -18
View File
@@ -24,14 +24,14 @@ function renderManager() {
beforeEach(() => {
localStorage.clear();
server.use(
http.get("http://localhost:8787/workspace-registry/status", () =>
http.get("/api/workspace-registry/status", () =>
HttpResponse.json({ branch: "main", head: "a".repeat(40), ahead: 0, behind: 0, degraded: false })),
http.get("http://localhost:8787/workspaces", () => HttpResponse.json([{
http.get("/api/workspaces", () => HttpResponse.json([{
id: "psd-clinical", name: "PSD Clinical", displayName: "PSD Clinical", description: "Clinical data",
language: "en", file: "workspaces/psd-clinical.yaml",
revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "operational" },
}])),
http.get("http://localhost:8787/workspaces/psd-clinical", () => HttpResponse.json({
http.get("/api/workspaces/psd-clinical", () => HttpResponse.json({
workspace,
revision: { id: "psd-clinical", commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/psd", state: "operational" },
})),
@@ -60,8 +60,8 @@ test("imports a bundle as a local draft and never publishes it automatically", a
const user = userEvent.setup();
const publishSpy = vi.fn();
server.use(
http.post("http://localhost:8787/workspaces/import", () => HttpResponse.json({ draft: { workspace, contract: {} } })),
http.post("http://localhost:8787/workspaces/publish", () => {
http.post("/api/workspaces/import", () => HttpResponse.json({ draft: { workspace, contract: {} } })),
http.post("/api/workspaces/publish", () => {
publishSpy();
return HttpResponse.json({});
}),
@@ -87,9 +87,9 @@ test("pulls and exports only when the manager explicitly requests each action",
vi.stubGlobal("URL", DownloadUrl);
vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(() => undefined);
server.use(
http.post("http://localhost:8787/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "c".repeat(40), ahead: 0, behind: 0, degraded: false })),
http.get("http://localhost:8787/workspaces/psd-clinical/export", () => new HttpResponse(new Blob(["bundle"], { type: "application/zip" }))),
http.post("http://localhost:8787/workspaces/publish", () => {
http.post("/api/workspace-registry/pull", () => HttpResponse.json({ branch: "main", head: "c".repeat(40), ahead: 0, behind: 0, degraded: false })),
http.get("/api/workspaces/psd-clinical/export", () => new HttpResponse(new Blob(["bundle"], { type: "application/zip" }))),
http.post("/api/workspaces/publish", () => {
publishSpy();
return HttpResponse.json({});
}),
@@ -109,7 +109,7 @@ test("pulls and exports only when the manager explicitly requests each action",
test("stages duplicate and delete operations without publishing", async () => {
const user = userEvent.setup();
let published = false;
server.use(http.post("http://localhost:8787/workspaces/publish", () => {
server.use(http.post("/api/workspaces/publish", () => {
published = true;
return HttpResponse.json({});
}));
@@ -131,8 +131,8 @@ test("saves resolved conflict choices as a rebased browser draft without publish
const local = { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "local-model" } } };
const remote = { ...workspace, semantic_index: { ...workspace.semantic_index, embedding: { ...workspace.semantic_index.embedding, model: "remote-model" } } };
server.use(
http.post("http://localhost:8787/workspaces/validate", () => HttpResponse.json({ workspace: local, contract: {} })),
http.post("http://localhost:8787/workspaces/publish", () => {
http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace: local, contract: {} })),
http.post("/api/workspaces/publish", () => {
publishCalls += 1;
return HttpResponse.json({
code: "workspace_conflict", message: "Workspace changed in the registry.", fields: ["semantic_index.embedding.model"],
@@ -162,11 +162,11 @@ test("proposes a different valid ID when duplicating a 63-character workspace ID
const maxId = `w${"a".repeat(62)}`;
const maxWorkspace = { ...workspace, workspace: { ...workspace.workspace, id: maxId } };
server.use(
http.get("http://localhost:8787/workspaces", () => HttpResponse.json([{
http.get("/api/workspaces", () => HttpResponse.json([{
id: maxId, name: "Maximum", displayName: "Maximum", language: "en", file: `workspaces/${maxId}.yaml`,
revision: { id: maxId, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/maximum", state: "operational" },
}])),
http.get(`http://localhost:8787/workspaces/${maxId}`, () => HttpResponse.json({
http.get(`/api/workspaces/${maxId}`, () => HttpResponse.json({
workspace: maxWorkspace,
revision: { id: maxId, commit: "a".repeat(40), blob: "b".repeat(40), snapshotPath: "/tmp/maximum", state: "operational" },
})),
@@ -197,8 +197,8 @@ test("does not show a saved-draft toast when manager validation rejects a DWH ti
test("runs validation and installation test with only sanitized messages", async () => {
const user = userEvent.setup();
server.use(
http.post("http://localhost:8787/workspaces/validate", () => HttpResponse.json({ workspace, contract: {} })),
http.post("http://localhost:8787/workspaces/psd-clinical/test", () => HttpResponse.json({
http.post("/api/workspaces/validate", () => HttpResponse.json({ workspace, contract: {} })),
http.post("/api/workspaces/psd-clinical/test", () => HttpResponse.json({
activatable: false,
diagnostics: [{ level: "warning", code: "binding_missing", field: "dwh", message: "DWH binding is not configured" }],
})),
@@ -216,7 +216,7 @@ test("runs validation and installation test with only sanitized messages", async
test("shows an accessible retry instead of a loading status when the registry status query fails", async () => {
const user = userEvent.setup();
let calls = 0;
server.use(http.get("http://localhost:8787/workspace-registry/status", () => {
server.use(http.get("/api/workspace-registry/status", () => {
calls += 1;
return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json({ branch: "main", ahead: 0, behind: 0, degraded: false });
}));
@@ -231,7 +231,7 @@ test("shows an accessible retry instead of a loading status when the registry st
test("shows an accessible retry instead of an empty list when the workspace list query fails", async () => {
const user = userEvent.setup();
let calls = 0;
server.use(http.get("http://localhost:8787/workspaces", () => {
server.use(http.get("/api/workspaces", () => {
calls += 1;
return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json([]);
}));
@@ -247,7 +247,7 @@ test("shows an accessible retry instead of an empty list when the workspace list
test("shows an accessible retry when the selected workspace detail query fails", async () => {
const user = userEvent.setup();
let calls = 0;
server.use(http.get("http://localhost:8787/workspaces/psd-clinical", () => {
server.use(http.get("/api/workspaces/psd-clinical", () => {
calls += 1;
return calls === 1 ? new HttpResponse(null, { status: 503 }) : HttpResponse.json({
workspace,