feat: add CI deployment and live progress

This commit is contained in:
2026-09-05 12:09:16 +00:00 Unverified
parent 321f4e807a
commit aa02826dbb
27 changed files with 2711 additions and 180 deletions
+463
View File
@@ -0,0 +1,463 @@
import { describe, expect, test } from "bun:test";
import { cleanupExpiredSessions, createApp } from "../../server/app";
import { hashToken, MemoryAuthStore } from "../../server/auth";
import { MemoryAuditStore } from "../../server/audit-store";
import { MemoryOperationStore } from "../../server/operation-store";
const now = Date.parse("2026-09-05T00:00:00.000Z");
function request(path: string, init: RequestInit = {}, token = "admin-token") {
const headers = new Headers(init.headers);
headers.set("authorization", `Bearer ${token}`);
return new Request(`https://kuber.astrxl.dev${path}`, { ...init, headers });
}
async function setup() {
const store = new MemoryAuthStore();
const auditStore = new MemoryAuditStore(() => new Date(now));
const operationStore = new MemoryOperationStore(() => new Date(now));
await store.putUser({
username: "admin",
passwordHash: "hash",
roles: ["admin"],
});
await store.putUser({
username: "ci",
passwordHash: "hash",
roles: ["operator"],
});
await store.putSession({
tokenHash: hashToken("admin-token"),
username: "admin",
authVersion: 1,
expiresAt: "2026-10-05T00:00:00.000Z",
});
return {
store,
auditStore,
operationStore,
app: createApp({ store, auditStore, operationStore, now: () => now }),
};
}
describe("API keys", () => {
test("creates once, lists without a token or hash, and revokes", async () => {
const { app, auditStore } = await setup();
const created = await app(
request("/api/v2/users/ci/keys", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
capabilities: ["kubernetes:read"],
workspace: "shop",
}),
}),
);
expect(created.status).toBe(201);
const key = (await created.json()) as { id: string; token: string };
expect(key.token).toHaveLength(43);
const listed = await app(request("/api/v2/users/ci/keys"));
const body = JSON.stringify(await listed.json());
expect(body).not.toContain(key.token);
expect(body).not.toContain(hashToken(key.token));
expect(JSON.stringify(await auditStore.list())).not.toContain(key.token);
expect(JSON.stringify(await auditStore.list())).not.toContain(
hashToken(key.token),
);
expect(
(
await app(
request(`/api/v2/users/ci/keys/${key.id}`, { method: "DELETE" }),
)
).status,
).toBe(204);
expect((await app(request("/api/v2/me", {}, key.token))).status).toBe(401);
});
test("uses key capabilities rather than owner roles and invalidates disabled owners", async () => {
const { app, store } = await setup();
await store.createApiKey({
id: "key_capability_test",
tokenHash: hashToken("ci-key"),
username: "ci",
capabilities: ["kubernetes:read"],
expiresAt: "2026-10-05T00:00:00.000Z",
});
expect((await app(request("/api/v2/users", {}, "ci-key"))).status).toBe(
403,
);
expect((await app(request("/api/v2/me", {}, "ci-key"))).status).toBe(200);
await store.updateUser("ci", { disabled: true });
expect((await app(request("/api/v2/me", {}, "ci-key"))).status).toBe(401);
});
test("limits API key children to the parent's user, capabilities, and workspace", async () => {
const { app, store, auditStore } = await setup();
await store.createApiKey({
id: "key_delegation_parent",
tokenHash: hashToken("delegation-parent"),
username: "ci",
capabilities: ["users:write", "kubernetes:read"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
const create = (body: unknown) =>
app(
request(
"/api/v2/users/ci/keys",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
},
"delegation-parent",
),
);
const capabilities = await create({
capabilities: ["kubernetes:write"],
workspace: "shop",
});
expect(capabilities.status).toBe(403);
const capabilityError = (await capabilities.json()) as { code: string };
expect(capabilityError.code).toBe("API_KEY_DELEGATION_FORBIDDEN");
const workspace = await create({ capabilities: ["kubernetes:read"] });
expect(workspace.status).toBe(403);
const differentWorkspace = await create({
capabilities: ["kubernetes:read"],
workspace: "other",
});
expect(differentWorkspace.status).toBe(403);
const subset = await create({
capabilities: ["kubernetes:read"],
workspace: "shop",
});
expect(subset.status).toBe(201);
expect(
((await subset.json()) as { capabilities: string[] }).capabilities,
).toEqual(["kubernetes:read"]);
const otherUser = await app(
request(
"/api/v2/users/admin/keys",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
capabilities: ["kubernetes:read"],
workspace: "shop",
}),
},
"delegation-parent",
),
);
expect(otherUser.status).toBe(403);
const denied = await auditStore.list();
expect(
denied.filter(
(event) =>
event.spec.action === "api_key.create" &&
event.spec.outcome === "denied",
),
).toHaveLength(4);
expect(JSON.stringify(denied)).not.toContain("delegation-parent");
await store.createApiKey({
id: "key_unscoped_delegation",
tokenHash: hashToken("unscoped-delegation"),
username: "ci",
capabilities: ["users:write", "kubernetes:read"],
expiresAt: "2026-10-05T00:00:00.000Z",
});
const unscopedSubset = await app(
request(
"/api/v2/users/ci/keys",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ capabilities: ["kubernetes:read"] }),
},
"unscoped-delegation",
),
);
expect(unscopedSubset.status).toBe(201);
expect(await unscopedSubset.json()).not.toHaveProperty("workspace");
});
test("rejects expired keys and expiry longer than 365 days", async () => {
const { app, store } = await setup();
await store.createApiKey({
id: "key_expiry_test_1",
tokenHash: hashToken("expired-key"),
username: "ci",
capabilities: ["kubernetes:read"],
expiresAt: "2026-09-04T00:00:00.000Z",
});
expect((await app(request("/api/v2/me", {}, "expired-key"))).status).toBe(
401,
);
expect(
(
await app(
request("/api/v2/users/ci/keys", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
capabilities: ["kubernetes:read"],
expiresAt: "2027-09-06T00:00:00.000Z",
}),
}),
)
).status,
).toBe(400);
});
test("uses the default expiry, cleans up expired keys, and does not log keys out", async () => {
const { app, store } = await setup();
const created = await app(
request("/api/v2/users/ci/keys", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ capabilities: ["kubernetes:read"] }),
}),
);
const key = (await created.json()) as { token: string; expiresAt: string };
expect(key.expiresAt).toBe("2026-12-04T00:00:00.000Z");
expect(
(await app(request("/api/v2/logout", { method: "POST" }, key.token)))
.status,
).toBe(204);
expect((await app(request("/api/v2/me", {}, key.token))).status).toBe(200);
expect(await cleanupExpiredSessions(store, Date.parse(key.expiresAt))).toBe(
2,
);
expect((await app(request("/api/v2/me", {}, key.token))).status).toBe(401);
});
test("denies a workspace-scoped key outside its workspace", async () => {
const { app, store } = await setup();
await store.createApiKey({
id: "key_scope_test_1",
tokenHash: hashToken("scoped-key"),
username: "ci",
capabilities: ["kubernetes:read"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
expect(
(await app(request("/api/v2/workspaces/other", {}, "scoped-key"))).status,
).toBe(403);
});
test("limits workspace-scoped keys to their own audit records", async () => {
const { app, store, auditStore } = await setup();
await store.createApiKey({
id: "key_audit_scope_1",
tokenHash: hashToken("scoped-audit-key"),
username: "ci",
capabilities: ["users:read"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
await auditStore.append({
actor: { username: "admin" },
action: "workspace.shop",
workspaceId: "shop",
outcome: "success",
});
await auditStore.append({
actor: { username: "admin" },
action: "workspace.other",
workspaceId: "other",
outcome: "success",
});
await auditStore.append({
actor: { username: "admin" },
action: "platform.global",
outcome: "success",
});
const unfiltered = await app(
request("/api/v2/audit", {}, "scoped-audit-key"),
);
expect(unfiltered.status).toBe(200);
const audit = (await unfiltered.json()) as {
items: { spec: { action: string } }[];
};
expect(audit.items.map((event) => event.spec.action)).toEqual([
"workspace.shop",
]);
expect(
(
await app(
request("/api/v2/audit?workspaceId=other", {}, "scoped-audit-key"),
)
).status,
).toBe(403);
});
test("automatically scopes unfiltered operation lists for workspace keys", async () => {
const { app, store, operationStore } = await setup();
await operationStore.create({
workspaceId: "shop",
action: "resources.apply",
idempotencyKey: "shop-operation",
});
await operationStore.create({
workspaceId: "other",
action: "resources.apply",
idempotencyKey: "other-operation",
});
await store.createApiKey({
id: "key_operation_scope_1",
tokenHash: hashToken("scoped-operation-key"),
username: "ci",
capabilities: ["kubernetes:read"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
const unfiltered = await app(
request("/api/v2/operations", {}, "scoped-operation-key"),
);
expect(unfiltered.status).toBe(200);
expect(
(
(await unfiltered.json()) as {
items: { spec: { workspaceId: string } }[];
}
).items.map((operation) => operation.spec.workspaceId),
).toEqual(["shop"]);
expect(
(
await app(
request(
"/api/v2/operations?workspaceId=shop",
{},
"scoped-operation-key",
),
)
).status,
).toBe(200);
expect(
(
await app(
request(
"/api/v2/operations?workspaceId=other",
{},
"scoped-operation-key",
),
)
).status,
).toBe(403);
});
test("does not inherit an admin owner's platform adoption privilege", async () => {
const { store } = await setup();
const adopted: string[] = [];
const app = createApp({
store,
adoption: {
adopt: async () => ({
workspaceId: "",
workspaceUid: "",
resourcesAdopted: 0,
}),
adoptPlatform: async (workspaceUid) => {
adopted.push(workspaceUid);
return {
workspaceId: "kuber-system",
workspaceUid,
resourcesAdopted: 1,
};
},
},
now: () => now,
});
await store.createApiKey({
id: "key_platform_owner",
tokenHash: hashToken("admin-owner-key"),
username: "admin",
capabilities: ["kubernetes:write"],
workspace: "kuber-system",
expiresAt: "2026-10-05T00:00:00.000Z",
});
await store.createApiKey({
id: "key_platform_scope",
tokenHash: hashToken("wrong-scope-key"),
username: "admin",
capabilities: ["platform:adopt"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
await store.createApiKey({
id: "key_platform_allowed",
tokenHash: hashToken("platform-key"),
username: "admin",
capabilities: ["platform:adopt"],
workspace: "kuber-system",
expiresAt: "2026-10-05T00:00:00.000Z",
});
const adopt = (token: string) =>
app(
request(
"/api/v2/platform/kuber-system/adopt",
{
method: "POST",
body: JSON.stringify({ workspaceUid: "platform" }),
},
token,
),
);
expect((await adopt("admin-owner-key")).status).toBe(403);
expect((await adopt("wrong-scope-key")).status).toBe(403);
expect((await adopt("platform-key")).status).toBe(200);
expect(adopted).toEqual(["platform"]);
});
test("allows a workspace-scoped key to use matching project build routes", async () => {
const { app, store } = await setup();
await store.createApiKey({
id: "key_scope_build_1",
tokenHash: hashToken("scoped-build-key"),
username: "ci",
capabilities: ["kubernetes:write"],
workspace: "shop",
expiresAt: "2026-10-05T00:00:00.000Z",
});
const matching = await app(
request(
"/api/v2/images/resolve",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ project: "shop", service: "web" }),
},
"scoped-build-key",
),
);
expect(matching.status).toBe(503);
expect(
(
await app(
request(
"/api/v2/images/resolve",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ project: "other", service: "web" }),
},
"scoped-build-key",
),
)
).status,
).toBe(403);
});
});
+190
View File
@@ -391,6 +391,94 @@ describe("kuber v2 HTTP routes", () => {
expect((await apply(app)).status).toBe(403);
});
test("starts preferred resource operations before returning so progress can be polled", 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 = "b".repeat(64);
let start!: () => void;
let finish!: () => void;
let complete!: () => void;
const started = new Promise<void>((resolve) => (start = resolve));
const unblock = new Promise<void>((resolve) => (finish = resolve));
const completed = new Promise<void>((resolve) => (complete = resolve));
const management = {
applyResources: async (
_workspace: unknown,
_resources: unknown,
execution: {
emit?: (event: {
resource: { apiVersion: string; kind: string; name: string };
phase: "apply";
state: "started" | "succeeded";
}) => Promise<void>;
},
) => {
await execution.emit?.({
resource: { apiVersion: "v1", kind: "Service", name: "web" },
phase: "apply",
state: "started",
});
start();
await unblock;
await execution.emit?.({
resource: { apiVersion: "v1", kind: "Service", name: "web" },
phase: "apply",
state: "succeeded",
});
complete();
return [];
},
} as unknown as ManagementService;
const app = createApp({
store: await authenticatedStore("operator"),
workspaceStore,
operationStore: new MemoryOperationStore(),
trustStore,
management,
});
await app(
request(
"/api/v2/workspaces/demo/trust",
{ method: "POST", body: JSON.stringify({ fingerprint }) },
"token",
),
);
const submitted = await app(
request(
"/api/v2/workspaces/demo/resources/apply",
{
method: "POST",
headers: {
"idempotency-key": "progress-once",
prefer: "respond-async",
"x-kuber-trust-project": "demo",
"x-kuber-trust-fingerprint": fingerprint,
},
body: JSON.stringify({ resources: [] }),
},
"token",
),
);
expect(submitted.status).toBe(202);
const { operationId } = (await submitted.json()) as { operationId: string };
await started;
const events = await app(
request(`/api/v2/operations/${operationId}/events?after=0`, {}, "token"),
);
expect(await events.json()).toMatchObject({
items: [{ sequence: 1, data: { state: "started" } }],
});
finish();
await completed;
});
test("uses exact origins, request IDs, and problem+json errors", async () => {
const app = createApp({
store: new MemoryAuthStore(),
@@ -548,6 +636,108 @@ describe("kuber v2 HTTP routes", () => {
expect(await operationStore.list("demo")).toHaveLength(1);
});
test("lists persisted operation events with capability and workspace scope checks", async () => {
const store = await authenticatedStore("operator");
const workspaceStore = new MemoryWorkspaceStore({
uid: () => "workspace-uid",
});
await workspaceStore.create({
id: "demo",
source: { uri: "oci://example/demo", digest: "sha256:abc" },
});
const operationStore = new MemoryOperationStore(
undefined,
() => "event-id",
);
const operation = await operationStore.create({
workspaceId: "demo",
action: "resources.apply",
idempotencyKey: "event-key",
});
await operationStore.emit(operation.metadata.name, {
resource: { apiVersion: "v1", kind: "Service", name: "web" },
phase: "apply",
state: "succeeded",
});
await store.createApiKey({
id: "demo-reader-key-01",
tokenHash: hashToken("demo-reader-token"),
username: "operator",
capabilities: ["kubernetes:read"],
workspace: "demo",
expiresAt: "2030-01-01T00:00:00.000Z",
});
await store.createApiKey({
id: "other-reader-key-1",
tokenHash: hashToken("other-reader-token"),
username: "operator",
capabilities: ["kubernetes:read"],
workspace: "other",
expiresAt: "2030-01-01T00:00:00.000Z",
});
const app = createApp({ store, workspaceStore, operationStore });
const path = `/api/v2/operations/${operation.metadata.name}/events?after=0`;
const allowed = await app(request(path, {}, "demo-reader-token"));
expect(allowed.status).toBe(200);
expect(await allowed.json()).toMatchObject({
items: [
{
sequence: 1,
data: { resource: { kind: "Service", name: "web" }, phase: "apply" },
},
],
nextCursor: 1,
retainedFirstSequence: 1,
cursorGap: false,
});
expect((await app(request(path, {}, "other-reader-token"))).status).toBe(
403,
);
expect(
(await app(request(`${path}x`, {}, "demo-reader-token"))).status,
).toBe(400);
});
test("reports operation event cursor gaps after retained history is truncated", async () => {
const operationStore = new MemoryOperationStore(
undefined,
() => "retained",
);
const operation = await operationStore.create({
workspaceId: "demo",
action: "resources.apply",
idempotencyKey: "retained-events",
});
for (let sequence = 0; sequence < 257; sequence += 1) {
await operationStore.emit(operation.metadata.name, {
resource: { apiVersion: "v1", kind: "ConfigMap", name: "config" },
phase: "apply",
state: "succeeded",
});
}
const app = createApp({
store: await authenticatedStore("operator"),
operationStore,
});
const response = await app(
request(
`/api/v2/operations/${operation.metadata.name}/events?after=0`,
{},
"token",
),
);
expect(response.status).toBe(200);
const events = (await response.json()) as {
retainedFirstSequence: number;
cursorGap: boolean;
items: { sequence: number }[];
};
expect(events.retainedFirstSequence).toBe(2);
expect(events.cursorGap).toBe(true);
expect(events.items[0]?.sequence).toBe(2);
});
test("rejects JSON bodies over the configured limit", async () => {
const app = createApp({
store: await authenticatedStore("admin"),
+45 -2
View File
@@ -14,7 +14,10 @@ import {
KubernetesWorkspacePersistence,
type LeaseObjects,
} from "../../server/kubernetes-state";
import type { Operation } from "../../server/operation-store";
import {
PersistentOperationStore,
type Operation,
} from "../../server/operation-store";
import type {
Workspace,
WorkspaceRevision,
@@ -183,6 +186,42 @@ describe("Kubernetes state persistence", () => {
);
});
test("keeps operation progress in the Kubernetes-backed operation record", async () => {
const fake = new FakeObjects();
const store = new PersistentOperationStore(
new KubernetesOperationPersistence(
fake as unknown as KubernetesObjectApi,
),
() => new Date(timestamp),
() => "event-id",
);
const created = await store.create({
workspaceId: "demo",
action: "resources.apply",
idempotencyKey: "events",
});
await store.emit(created.metadata.name, {
resource: { apiVersion: "v1", kind: "ConfigMap", name: "settings" },
phase: "apply",
state: "succeeded",
});
const reloaded = new PersistentOperationStore(
new KubernetesOperationPersistence(
fake as unknown as KubernetesObjectApi,
),
);
expect(await reloaded.events(created.metadata.name)).toMatchObject({
items: [
expect.objectContaining({
sequence: 1,
data: expect.objectContaining({ phase: "apply" }),
}),
],
retainedFirstSequence: 1,
cursorGap: false,
});
});
test("recovers replacement from a matching precreated revision", async () => {
const fake = new FakeObjects();
const persistence = new KubernetesWorkspacePersistence(
@@ -365,7 +404,11 @@ class FakeLeaseStore implements LeaseObjects {
return structuredClone(stored);
}
async delete(name: string, namespace: string, expectedResourceVersion?: string) {
async delete(
name: string,
namespace: string,
expectedResourceVersion?: string,
) {
this.beforeDelete?.();
this.beforeDelete = undefined;
const key = this.key(name, namespace);
+39 -1
View File
@@ -49,8 +49,10 @@ class FakeObjects {
readonly secrets = new Map<string, StoredSecret>();
readonly patches: StoredSecret[] = [];
readonly deleted: string[] = [];
reads = 0;
async read(value: KubernetesObject): Promise<StoredSecret> {
this.reads += 1;
const found = this.secrets.get(value.metadata?.name ?? "");
if (!found) throw { code: 404 };
return found;
@@ -97,7 +99,10 @@ class FakeObjects {
}
}
function setup(): { fake: FakeObjects; store: KubernetesAuthStore } {
function setup(): {
fake: FakeObjects;
store: KubernetesAuthStore;
} {
const fake = new FakeObjects();
return {
fake,
@@ -106,6 +111,39 @@ function setup(): { fake: FakeObjects; store: KubernetesAuthStore } {
}
describe("KubernetesAuthStore", () => {
test("validates the session and user from the store on every request", async () => {
const { fake, store } = setup();
const tokenHash = hashToken("fresh");
await store.putUser({
username: "alice",
passwordHash: "hash",
roles: ["viewer"],
});
await store.putSession({
tokenHash,
username: "alice",
roles: ["viewer"],
expiresAt: "2026-09-03T00:00:00.000Z",
});
fake.reads = 0;
await expect(store.getSession(tokenHash)).resolves.toMatchObject({
tokenHash,
});
fake.secrets.set(
objectName("user", "alice"),
secret("user", objectName("user", "alice"), {
username: "alice",
passwordHash: "hash",
roles: JSON.stringify(["viewer"]),
authVersion: "1",
disabled: "true",
}),
);
await expect(store.getSession(tokenHash)).resolves.toBeUndefined();
expect(fake.reads).toBe(4);
});
test("persists authVersion and not copied roles in new sessions", async () => {
const { fake, store } = setup();
await store.putUser({
+73
View File
@@ -163,6 +163,79 @@ describe("server management service", () => {
expect(deleted).toEqual(plan.stale);
});
test("emits safe per-resource apply, wait, delete, and failure progress", async () => {
const events: Array<{
phase: string;
state: string;
resource: { name: string };
}> = [];
const service = createManagementService(
dependencies({
listDeployments: async () => [
object("Deployment", "web", "web-uid") as V1Deployment,
],
applyResource: async (resource) => resource,
deleteResource: async () => {},
}),
);
const execution = {
emit: async (event: (typeof events)[number]) => {
events.push(event);
},
};
await service.applyResources(
workspace,
[object("Service", "web", "service-uid")],
execution,
);
await service.waitForResources(workspace, ["web"], undefined, execution);
await service.deleteResources(
workspace,
[
{
apiVersion: "v1",
kind: "Service",
name: "old",
uid: "old-uid",
workspaceUid: workspace.uid,
},
],
execution,
);
expect(
events.map(
({ phase, state, resource }) => `${phase}:${resource.name}:${state}`,
),
).toEqual([
"apply:web:started",
"apply:web:succeeded",
"wait:web:started",
"wait:web:succeeded",
"delete:old:started",
"delete:old:succeeded",
]);
const failing = createManagementService(
dependencies({
applyResource: async () => {
throw new Error("provider failed");
},
}),
);
await expect(
failing.applyResources(
workspace,
[object("Service", "broken", "broken-uid")],
execution,
),
).rejects.toThrow("provider failed");
expect(events.at(-1)).toMatchObject({
phase: "apply",
state: "failed",
resource: { name: "broken" },
});
});
test("rejects namespace and resource ownership mismatches", async () => {
const wrongNamespace = createManagementService(
dependencies({
+53
View File
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
import {
MemoryOperationStore,
MemoryWorkspaceLeaseProvider,
MAX_OPERATION_EVENTS,
OperationConflictError,
OperationValidationError,
recoverStaleOperations,
@@ -153,6 +154,58 @@ describe("operation store", () => {
).rejects.toBeInstanceOf(OperationValidationError);
});
test("persists monotonic bounded progress events with cursors", async () => {
const store = new MemoryOperationStore(undefined, () => "event-operation");
const operation = await store.create({
workspaceId: "demo",
action: "resources.apply",
idempotencyKey: "events",
});
await store.emit(operation.metadata.name, {
resource: { apiVersion: "v1", kind: "Secret", name: "credentials" },
phase: "apply",
state: "started",
});
const complete = await store.emit(operation.metadata.name, {
resource: { apiVersion: "v1", kind: "Secret", name: "credentials" },
phase: "apply",
state: "succeeded",
});
expect(complete.sequence).toBe(2);
expect(await store.events(operation.metadata.name, 1)).toMatchObject({
items: [expect.objectContaining({ sequence: 2 })],
retainedFirstSequence: 1,
cursorGap: false,
});
await store.transition(operation.metadata.name, "running");
await store.transition(operation.metadata.name, "succeeded");
expect(await store.events(operation.metadata.name, 1)).toMatchObject({
items: [expect.objectContaining({ sequence: 2 })],
retainedFirstSequence: 1,
cursorGap: false,
});
await expect(
store.events(operation.metadata.name, -1),
).rejects.toBeInstanceOf(OperationValidationError);
for (let index = 0; index < MAX_OPERATION_EVENTS; index++) {
await store.emit(operation.metadata.name, {
resource: { apiVersion: "v1", kind: "Service", name: `web-${index}` },
phase: "apply",
state: "succeeded",
});
}
const bounded = await store.events(operation.metadata.name);
expect(bounded.items).toHaveLength(MAX_OPERATION_EVENTS);
expect(bounded.items[0]?.sequence).toBe(3);
expect(bounded).toMatchObject({
retainedFirstSequence: 3,
cursorGap: true,
});
expect(await store.events(operation.metadata.name, 2)).toMatchObject({
cursorGap: false,
});
});
test("provides exclusive, renewable per-workspace leases", async () => {
let now = 0;
const leases = new MemoryWorkspaceLeaseProvider(() => now);
+63
View File
@@ -54,6 +54,69 @@ describe("OCI registry digest resolution", () => {
]);
});
test("caches successful digest resolutions with deterministic expiry and bounds", async () => {
let now = 0;
let calls = 0;
const fetcher = async () => {
calls += 1;
return new Response("manifest", {
headers: { "docker-content-digest": `sha256:${"d".repeat(64)}` },
});
};
const options = {
fetch: fetcher,
cacheTtlMs: 100,
cacheMaxEntries: 1,
clock: () => now,
};
await resolveRegistryDigest("cache.example.com/team/first:v1", options);
await resolveRegistryDigest("cache.example.com/team/first:v1", options);
expect(calls).toBe(1);
now = 100;
await resolveRegistryDigest("cache.example.com/team/first:v1", options);
expect(calls).toBe(2);
await resolveRegistryDigest("cache.example.com/team/second:v1", options);
await resolveRegistryDigest("cache.example.com/team/first:v1", options);
expect(calls).toBe(4);
});
test("does not cache failed resolutions or share entries across credentials", async () => {
let calls = 0;
const failing = async () => {
calls += 1;
return new Response("unavailable", { status: 503 });
};
const options = { fetch: failing, cacheTtlMs: 1_000 };
await expect(
resolveRegistryDigest("failure.example.com/team/image:v1", options),
).rejects.toThrow("503");
await expect(
resolveRegistryDigest("failure.example.com/team/image:v1", options),
).rejects.toThrow("503");
expect(calls).toBe(2);
const authorizedCalls: string[] = [];
const authorized = async (
_input: string | URL | Request,
init?: RequestInit,
) => {
authorizedCalls.push(new Headers(init?.headers).get("authorization")!);
return new Response("manifest", {
headers: { "docker-content-digest": `sha256:${"e".repeat(64)}` },
});
};
for (const username of ["one", "two"]) {
await resolveRegistryDigest("credentials.example.com/team/image:v1", {
fetch: authorized,
credentials: { username, password: "password" },
});
}
expect(authorizedCalls).toHaveLength(2);
});
test("resolves through a trusted internal registry origin", async () => {
const expected = `sha256:${"c".repeat(64)}` as const;
let requested = "";