Files
kuber/tests/server/build-store.test.ts
T
2026-09-03 11:28:30 +07:00

111 lines
3.4 KiB
TypeScript

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"): 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: "2026-09-02T00:00:00.000Z",
};
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);
await expect(store.createBuild(build("two"))).rejects.toBeInstanceOf(
BuildStoreConflictError,
);
const first = (await store.getBuild("one"))!;
const digest = `sha256:${"b".repeat(64)}` as Sha256Digest;
first.metadata.resourceVersion = "2";
Object.assign(first.status, {
state: "succeeded",
digest,
finishedAt: first.status.createdAt,
});
await store.replaceBuild(first, "1");
expect((await store.createBuild(build("two"))).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, "2")).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,
);
});
});