import { describe, expect, it } from "vitest"; import { createMemoryToolcraftBinaryAssetRepository } from "./memory-binary-asset-repository"; const binaryOptions = { contentType: "model/gltf-binary", durable: true, } as const; describe("memory binary asset repository", () => { it("isolates staged entries until an atomic commit and returns defensive copies", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const owner = await repository.beginLease("owner"); const observer = await repository.beginLease("observer"); const bytes = new Uint8Array([1, 2, 3]); await owner.put("source:a", bytes, binaryOptions); bytes[0] = 99; expect(owner.refs()).toEqual(["source:a"]); await expect(owner.get("source:a")).resolves.toMatchObject({ bytes: new Uint8Array([1, 2, 3]), contentType: "model/gltf-binary", durable: true, ref: "source:a", }); await expect(observer.get("source:a")).resolves.toBeNull(); await expect(repository.get("source:a")).resolves.toBeNull(); await owner.commit(); const firstRead = await repository.get("source:a"); expect(firstRead?.bytes).toEqual(new Uint8Array([1, 2, 3])); firstRead!.bytes[1] = 88; await expect(repository.get("source:a")).resolves.toMatchObject({ bytes: new Uint8Array([1, 2, 3]), }); }); it("rolls back every staged entry and makes completed leases terminal", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const rolledBack = await repository.beginLease("rollback"); await rolledBack.put("source:a", new Uint8Array([1]), binaryOptions); await rolledBack.put("document:a", new Uint8Array([2]), { contentType: "application/vnd.toolcraft.model-document", durable: false, }); await rolledBack.rollback(); await expect(repository.get("source:a")).resolves.toBeNull(); await expect(repository.get("document:a")).resolves.toBeNull(); await expect(rolledBack.get("source:a")).rejects.toThrow(/terminal/u); expect(rolledBack.refs()).toEqual([]); await expect( rolledBack.put("later", new Uint8Array([3]), binaryOptions), ).rejects.toThrow(/terminal/u); await expect(rolledBack.commit()).rejects.toThrow(/terminal/u); await expect(rolledBack.rollback()).rejects.toThrow(/terminal/u); const committed = await repository.beginLease("commit"); await committed.put("source:b", new Uint8Array([4]), binaryOptions); await committed.commit(); await expect(committed.get("source:b")).rejects.toThrow(/terminal/u); expect(committed.refs()).toEqual([]); await expect( committed.put("later", new Uint8Array([5]), binaryOptions), ).rejects.toThrow(/terminal/u); await expect(committed.commit()).rejects.toThrow(/terminal/u); await expect(committed.rollback()).rejects.toThrow(/terminal/u); }); it("keeps identical same-lease puts idempotent and rejects conflicting replacements", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const lease = await repository.beginLease("same-lease-duplicate"); await lease.put("shared", new Uint8Array([1, 2]), binaryOptions); await lease.put("shared", new Uint8Array([1, 2]), binaryOptions); await expect( lease.put("shared", new Uint8Array([1, 3]), binaryOptions), ).rejects.toThrow(/shared/u); await expect( lease.put("shared", new Uint8Array([1, 2]), { ...binaryOptions, contentType: "application/octet-stream", }), ).rejects.toThrow(/shared/u); await expect( lease.put("shared", new Uint8Array([1, 2]), { ...binaryOptions, durable: false, }), ).rejects.toThrow(/shared/u); expect(lease.refs()).toEqual(["shared"]); await expect(lease.get("shared")).resolves.toMatchObject({ bytes: new Uint8Array([1, 2]), contentType: binaryOptions.contentType, durable: true, }); }); it("rejects conflicting duplicate refs without publishing any part of the lease", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const first = await repository.beginLease("first"); await first.put("shared", new Uint8Array([1]), binaryOptions); await first.commit(); const conflicting = await repository.beginLease("conflicting"); await conflicting.put("shared", new Uint8Array([2]), binaryOptions); await conflicting.put("unique", new Uint8Array([3]), binaryOptions); await expect(conflicting.commit()).rejects.toThrow(/shared/u); await expect(repository.get("shared")).resolves.toMatchObject({ bytes: new Uint8Array([1]), }); await expect(repository.get("unique")).resolves.toBeNull(); await conflicting.rollback(); const identical = await repository.beginLease("identical"); await identical.put("shared", new Uint8Array([1]), binaryOptions); await identical.put("published", new Uint8Array([4]), binaryOptions); await identical.commit(); await expect(repository.get("published")).resolves.toMatchObject({ bytes: new Uint8Array([4]), }); }); it("deterministically gives the first concurrent commit an immutable duplicate ref", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const first = await repository.beginLease("first-concurrent"); const second = await repository.beginLease("second-concurrent"); await first.put("shared", new Uint8Array([1]), binaryOptions); await first.put("first-only", new Uint8Array([2]), binaryOptions); await second.put("shared", new Uint8Array([3]), binaryOptions); await second.put("second-only", new Uint8Array([4]), binaryOptions); const results = await Promise.allSettled([first.commit(), second.commit()]); expect(results.map((result) => result.status)).toEqual([ "fulfilled", "rejected", ]); await expect(repository.get("shared")).resolves.toMatchObject({ bytes: new Uint8Array([1]), }); await expect(repository.get("first-only")).resolves.not.toBeNull(); await expect(repository.get("second-only")).resolves.toBeNull(); await second.rollback(); }); it("collects only unreachable committed refs in deterministic order", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const committed = await repository.beginLease("committed"); await committed.put("z-ref", new Uint8Array([1]), binaryOptions); await committed.put("keep", new Uint8Array([2]), binaryOptions); await committed.put("a-ref", new Uint8Array([3]), binaryOptions); await committed.commit(); const staged = await repository.beginLease("staged"); await staged.put("staged-only", new Uint8Array([4]), binaryOptions); await expect(repository.collect(new Set(["keep"]))).resolves.toEqual([ "a-ref", "z-ref", ]); await expect(repository.get("keep")).resolves.not.toBeNull(); await expect(repository.get("a-ref")).resolves.toBeNull(); await expect(staged.get("staged-only")).resolves.not.toBeNull(); await staged.commit(); await expect(repository.get("staged-only")).resolves.not.toBeNull(); }); it("compensates for a post-commit failure by collecting the unreferenced publication", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const lease = await repository.beginLease("post-commit-compensation"); await lease.put("orphaned-publication", new Uint8Array([1]), binaryOptions); await lease.commit(); await expect(lease.rollback()).rejects.toThrow(/terminal/u); await expect(repository.get("orphaned-publication")).resolves.not.toBeNull(); await expect(repository.collect(new Set())).resolves.toEqual([ "orphaned-publication", ]); await expect(repository.get("orphaned-publication")).resolves.toBeNull(); }); it("disposes repository ownership and invalidates existing leases", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const lease = await repository.beginLease("disposed-owner"); await lease.put("staged", new Uint8Array([1]), binaryOptions); await repository.dispose(); await expect(repository.dispose()).resolves.toBeUndefined(); await expect(repository.beginLease("later")).rejects.toThrow(/disposed/u); await expect(repository.get("staged")).rejects.toThrow(/disposed/u); await expect(repository.collect(new Set())).rejects.toThrow(/disposed/u); await expect(lease.get("staged")).rejects.toThrow(/disposed|terminal/u); await expect( lease.put("later", new Uint8Array([2]), binaryOptions), ).rejects.toThrow(/disposed|terminal/u); await expect(lease.commit()).rejects.toThrow(/disposed|terminal/u); await expect(lease.rollback()).rejects.toThrow(/disposed|terminal/u); expect(lease.refs()).toEqual([]); }); });