feat: v2
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user