import { describe, expect, test } from "bun:test"; import { BUILD_RECORD_API_VERSION, BuildStoreConflictError, MemoryBuildStore, type BuildRecord, } from "../../server/build-store"; import { BUILD_PROTOCOL_VERSION, type BuildRequest, type Sha256Digest, } from "../../shared/build-protocol"; const workspace = `sha256:${"a".repeat(64)}` as Sha256Digest; function build( id: string, imageKey = "project\0service\0image", createdAt = "2026-09-02T00:00:00.000Z", ): BuildRecord { const request: BuildRequest = { version: BUILD_PROTOCOL_VERSION, id, project: "project", service: "service", spec: { architecture: "amd64", image: "registry.test/app:latest", context: ".", buildArgs: [], workspace, }, }; const status = { version: BUILD_PROTOCOL_VERSION, id, state: "queued" as const, createdAt, }; return { apiVersion: BUILD_RECORD_API_VERSION, kind: "BuildRecord", metadata: { name: id, resourceVersion: "1", creationTimestamp: status.createdAt, labels: {}, }, spec: { request, imageKey, jobName: `job-${id}`, workspaceSubPath: id }, status: { ...status, logBytes: 0, logOffset: 0, nextSequence: 1, events: [{ type: "status", status }], }, }; } describe("build store", () => { test("atomically enforces idempotency and one active image build", async () => { const store = new MemoryBuildStore(); expect((await store.createBuild(build("one"))).created).toBe(true); expect((await store.createBuild(build("one"))).created).toBe(false); await expect( store.createBuild(build("one", "different-key")), ).rejects.toBeInstanceOf(BuildStoreConflictError); const replacement = await store.createBuild(build("two")); expect(replacement.created).toBe(true); expect(replacement.superseded?.[0]?.metadata.name).toBe("one"); const first = (await store.getBuild("one"))!; const digest = `sha256:${"b".repeat(64)}` as Sha256Digest; first.metadata.resourceVersion = "3"; Object.assign(first.status, { state: "succeeded", digest, finishedAt: first.status.createdAt, }); await store.replaceBuild(first, "2"); expect( (await store.createBuild(build("three", "2026-09-02T00:00:02.000Z"))) .created, ).toBe(true); const changed = (await store.getBuild("one"))!; changed.metadata.resourceVersion = "3"; changed.status.digest = `sha256:${"c".repeat(64)}`; await expect(store.replaceBuild(changed, "3")).rejects.toThrow("immutable"); }); test("optimistically updates resumable upload records without leaking mutable data", async () => { const store = new MemoryBuildStore(); const digest = `sha256:${"d".repeat(64)}` as Sha256Digest; const upload = { apiVersion: BUILD_RECORD_API_VERSION, kind: "BuildUpload" as const, metadata: { name: "upload", resourceVersion: "1", creationTimestamp: "now", }, spec: { digest, size: 3 }, status: { offset: 0, data: new Uint8Array() }, }; await store.createUpload(upload); upload.status.data = Buffer.from("mutated"); expect((await store.getUpload(digest))?.status.data.byteLength).toBe(0); const next = (await store.getUpload(digest))!; next.metadata.resourceVersion = "2"; next.status = { offset: 3, data: Buffer.from("abc") }; await store.replaceUpload(next, "1"); await expect(store.replaceUpload(next, "1")).rejects.toBeInstanceOf( BuildStoreConflictError, ); }); test("supersedes only the current image and leaves terminal history", async () => { const store = new MemoryBuildStore(); await store.createBuild( build("old", "project\0service\0image", "2026-09-02T00:00:00.000Z"), ); const result = await store.createBuild( build("new", "project\0service\0image", "2026-09-02T00:00:01.000Z"), ); expect(result.superseded?.[0]?.status.error).toBe( "Superseded by newer build", ); expect((await store.getBuild("old"))?.status.state).toBe("failed"); expect((await store.createBuild(build("new"))).created).toBe(false); expect( (await store.createBuild(build("other", "project\0service\0other"))) .created, ).toBe(true); }); test("concurrent same-image submissions have one active record", async () => { const store = new MemoryBuildStore(); const results = await Promise.all([ store.createBuild(build("first")), store.createBuild(build("second")), store.createBuild(build("third")), ]); expect(results.filter((result) => result.created)).toHaveLength(3); const active = (await store.listBuilds()).filter( (record) => record.status.state !== "succeeded" && record.status.state !== "failed", ); expect(active).toHaveLength(1); expect(active[0]?.metadata.name).toBe("third"); }); test("recovers an active record whose in-memory lock was lost", async () => { const store = new MemoryBuildStore(); await store.createBuild(build("orphan")); (store as unknown as { active: Map }).active.clear(); const replacement = await store.createBuild(build("replacement")); expect(replacement.superseded?.[0]?.metadata.name).toBe("orphan"); expect((await store.getBuild("orphan"))?.status.state).toBe("failed"); expect( await store.ownsBuild("project\0service\0image", "replacement"), ).toBe(true); }); test("a delayed older retry cannot reclaim a newer same-image lock", async () => { const store = new MemoryBuildStore(); await store.createBuild( build("a", "project\0service\0image", "2026-09-02T00:00:00.000Z"), ); (store as unknown as { active: Map }).active.clear(); await store.createBuild( build("b", "project\0service\0image", "2026-09-02T00:00:01.000Z"), ); const delayed = await store.createBuild( build("a", "project\0service\0image", "2026-09-02T00:00:00.000Z"), ); expect(delayed.created).toBe(false); expect(delayed.record.status.error).toBe("Superseded by newer build"); expect(await store.ownsBuild("project\0service\0image", "b")).toBe(true); }); test("returns cleanup metadata when a delayed candidate loses to a newer record", async () => { const store = new MemoryBuildStore(); await store.createBuild( build("newer", "project\0service\0image", "2026-09-02T00:00:01.000Z"), ); const result = await store.createBuild( build("delayed", "project\0service\0image", "2026-09-02T00:00:00.000Z"), ); expect(result.created).toBe(false); expect(result.superseded?.map((record) => record.metadata.name)).toEqual([ "delayed", ]); expect( (await store.createBuild(build("delayed"))).superseded, ).toBeUndefined(); }); test("orders and reconciles legacy records with invalid metadata timestamps", async () => { const store = new MemoryBuildStore(); const legacy = build( "legacy", "project\0service\0image", "2026-09-02T00:00:00.000Z", ); legacy.metadata.creationTimestamp = undefined as unknown as string; await store.createBuild(legacy); const replacement = await store.createBuild( build( "replacement", "project\0service\0image", "2026-09-02T00:00:01.000Z", ), ); expect(replacement.created).toBe(true); expect((await store.getBuild("legacy"))?.status.state).toBe("failed"); const malformed = build("a", "project\0service\0other"); malformed.metadata.creationTimestamp = 0 as unknown as string; await store.createBuild(malformed); expect( (await store.listBuilds()).map((record) => record.metadata.name), ).toEqual(["a", "legacy", "replacement"]); }); });