fix: stabilize live deployment progress

This commit is contained in:
2026-09-05 16:02:02 +00:00 Unverified
parent aa02826dbb
commit 2f0afaadc7
8 changed files with 544 additions and 55 deletions
+64
View File
@@ -3,6 +3,9 @@ import { cleanupExpiredSessions, createApp } from "../../server/app";
import { hashToken, MemoryAuthStore } from "../../server/auth";
import { MemoryAuditStore } from "../../server/audit-store";
import { MemoryOperationStore } from "../../server/operation-store";
import type { ManagementService } from "../../server/management";
import { MemoryTrustStore } from "../../server/trust-store";
import { MemoryWorkspaceStore } from "../../server/workspace-store";
const now = Date.parse("2026-09-05T00:00:00.000Z");
@@ -255,6 +258,67 @@ describe("API keys", () => {
).toBe(403);
});
test("allows scoped keys to apply only in their workspace and rejects deleted owners", async () => {
const { store } = await setup();
const workspaceStore = new MemoryWorkspaceStore({
uid: () => "workspace-uid",
});
for (const id of ["shop", "other"])
await workspaceStore.create({
id,
source: { uri: `oci://example/${id}`, digest: "sha256:abc" },
});
const trustStore = new MemoryTrustStore();
const fingerprint = "a".repeat(64);
await trustStore.grant("shop", fingerprint);
let applies = 0;
const app = createApp({
store,
workspaceStore,
trustStore,
operationStore: new MemoryOperationStore(() => new Date(now)),
management: {
applyResources: async () => {
applies += 1;
return [];
},
} as unknown as ManagementService,
now: () => now,
});
await store.createApiKey({
id: "key_apply_scope_1",
tokenHash: hashToken("scoped-apply-key"),
username: "ci",
capabilities: ["kubernetes:write"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
const apply = (workspace: string) =>
app(
request(
`/api/v2/workspaces/${workspace}/resources/apply`,
{
method: "POST",
headers: {
"idempotency-key": `apply-${workspace}`,
"x-kuber-trust-project": "shop",
"x-kuber-trust-fingerprint": fingerprint,
},
body: JSON.stringify({ resources: [] }),
},
"scoped-apply-key",
),
);
expect((await apply("shop")).status).toBe(200);
expect((await apply("other")).status).toBe(403);
expect(applies).toBe(1);
await store.deleteUser("ci");
expect(
(await app(request("/api/v2/me", {}, "scoped-apply-key"))).status,
).toBe(401);
});
test("limits workspace-scoped keys to their own audit records", async () => {
const { app, store, auditStore } = await setup();
await store.createApiKey({
+45
View File
@@ -391,6 +391,51 @@ describe("kuber v2 HTTP routes", () => {
expect((await apply(app)).status).toBe(403);
});
test("returns OPERATION_CONFLICT when an idempotency key is reused for another body", async () => {
const workspaceStore = new MemoryWorkspaceStore({
uid: () => "workspace-uid",
});
await workspaceStore.create({
id: "demo",
source: { uri: "oci://example/demo", digest: "sha256:abc" },
});
const trustStore = new MemoryTrustStore();
const fingerprint = "c".repeat(64);
await trustStore.grant("demo", fingerprint);
const app = createApp({
store: await authenticatedStore("operator"),
workspaceStore,
trustStore,
operationStore: new MemoryOperationStore(),
management: {
applyResources: async () => [],
} as unknown as ManagementService,
});
const apply = (resources: unknown[]) =>
app(
request(
"/api/v2/workspaces/demo/resources/apply",
{
method: "POST",
headers: {
"idempotency-key": "same-apply",
"x-kuber-trust-project": "demo",
"x-kuber-trust-fingerprint": fingerprint,
},
body: JSON.stringify({ resources }),
},
"token",
),
);
expect((await apply([])).status).toBe(200);
const conflict = await apply([
{ apiVersion: "v1", kind: "Service", metadata: { name: "web" } },
]);
expect(conflict.status).toBe(409);
expect(await conflict.json()).toMatchObject({ code: "OPERATION_CONFLICT" });
});
test("starts preferred resource operations before returning so progress can be polled", async () => {
const workspaceStore = new MemoryWorkspaceStore({
uid: () => "workspace-uid",
+29
View File
@@ -236,6 +236,35 @@ describe("server management service", () => {
});
});
test("emits aborted resource progress when an execution signal aborts", async () => {
const controller = new AbortController();
const events: Array<{ state: string; resource: { name: string } }> = [];
const service = createManagementService(
dependencies({
applyResource: async (_resource, execution) => {
controller.abort();
if (execution?.signal?.aborted)
throw new Error("Workspace operation execution was cancelled");
return _resource;
},
}),
);
await expect(
service.applyResources(
workspace,
[object("Service", "web", "service-uid")],
{
signal: controller.signal,
emit: async (event) => {
events.push(event);
},
},
),
).rejects.toThrow("cancelled");
expect(events.map(({ state }) => state)).toEqual(["started", "aborted"]);
});
test("rejects namespace and resource ownership mismatches", async () => {
const wrongNamespace = createManagementService(
dependencies({