import { afterEach, describe, expect, it, vi } from "vitest"; import { acquireToolcraftSourceAssetOwner } from "./toolcraft-source-asset-owner"; import { createTestSourceAssetCoordinator, createTestSourceAssetStore, } from "./toolcraft-source-asset-test-support"; function createDeferred() { let resolve!: (value: T) => void; const promise = new Promise((resolvePromise) => { resolve = resolvePromise; }); return { promise, resolve }; } afterEach(() => vi.unstubAllGlobals()); describe("Toolcraft source asset owner", () => { it("binds the resolver without relying on the proof decorator", async () => { type ReceiverAwareCoordinator = ReturnType< typeof createTestSourceAssetCoordinator > & Readonly<{ resolverBytes: Uint8Array }>; const resolverBytes = new Uint8Array([7, 8, 9]); const coordinator: ReceiverAwareCoordinator = { ...createTestSourceAssetCoordinator(), resolverBytes, resolveResource: vi.fn(async function ( this: ReceiverAwareCoordinator, ) { expect(this).toBe(coordinator); return this.resolverBytes; }), }; const lease = acquireToolcraftSourceAssetOwner({ createCoordinator: () => coordinator, store: createTestSourceAssetStore(), }); expect(lease.coordinator).toBe(coordinator); const resolveResource = lease.coordinator.resolveResource; if (!resolveResource) throw new Error("Expected a resource resolver."); await expect(resolveResource( "media:image:production-receiver", { signal: new AbortController().signal }, )).resolves.toBe(resolverBytes); lease.release(); }); it("attempts every cleanup and reports their failures once", async () => { const presentationFailure = new Error("presentation cleanup failed"); const coordinatorFailure = new Error("coordinator cleanup failed"); const releaseResourceRef = vi.fn(() => { throw presentationFailure; }); const disposeCoordinator = vi.fn(async () => { throw coordinatorFailure; }); const onError = vi.fn(); const store = createTestSourceAssetStore(); const lease = acquireToolcraftSourceAssetOwner({ createCoordinator: () => createTestSourceAssetCoordinator({ dispose: disposeCoordinator, resolveResource: vi.fn(async () => new Uint8Array([1, 2, 3])), retainResourceRef: () => releaseResourceRef, }), onError, store, }); const resources = lease.presentationResources; if (!resources) throw new Error("Expected presentation resources."); await resources.retain("media:image:cleanup", "image/png"); lease.release(); await vi.waitFor(() => expect(onError).toHaveBeenCalledTimes(1)); expect(releaseResourceRef).toHaveBeenCalledTimes(1); expect(disposeCoordinator).toHaveBeenCalledTimes(1); const reported = onError.mock.calls[0]?.[0]; expect(reported).toBeInstanceOf(AggregateError); expect((reported as AggregateError).errors).toEqual([ presentationFailure, coordinatorFailure, ]); expect(() => acquireToolcraftSourceAssetOwner({ store })).toThrow( "Toolcraft source asset owner failed to dispose.", ); }); it("keeps a disposing owner registered until cleanup settles", async () => { const disposal = createDeferred(); const store = createTestSourceAssetStore(); const firstCoordinator = createTestSourceAssetCoordinator({ dispose: vi.fn(() => disposal.promise), }); const first = acquireToolcraftSourceAssetOwner({ createCoordinator: () => firstCoordinator, store, }); first.release(); first.release(); await Promise.resolve(); const createReplacement = vi.fn(() => createTestSourceAssetCoordinator()); expect(() => acquireToolcraftSourceAssetOwner({ createCoordinator: createReplacement, store, })).toThrow("Toolcraft source asset owner is disposing."); expect(createReplacement).not.toHaveBeenCalled(); disposal.resolve(undefined); await vi.waitFor(() => expect(firstCoordinator.dispose).toHaveBeenCalledTimes(1) ); await Promise.resolve(); const replacement = acquireToolcraftSourceAssetOwner({ createCoordinator: createReplacement, store, }); expect(createReplacement).toHaveBeenCalledTimes(1); replacement.release(); }); it("reuses the active store owner regardless of later construction inputs", () => { const store = createTestSourceAssetStore(); const firstFactory = vi.fn(() => createTestSourceAssetCoordinator()); const replacementFactory = vi.fn(() => createTestSourceAssetCoordinator()); const firstReporter = vi.fn(); const replacementReporter = vi.fn(); const first = acquireToolcraftSourceAssetOwner({ createCoordinator: firstFactory, onError: firstReporter, store, }); const second = acquireToolcraftSourceAssetOwner({ createCoordinator: replacementFactory, onError: replacementReporter, store, }); const failure = new Error("existing owner operation failed"); expect(second.coordinator).toBe(first.coordinator); expect(replacementFactory).not.toHaveBeenCalled(); second.reportError(failure); expect(firstReporter).toHaveBeenCalledWith(failure); expect(replacementReporter).not.toHaveBeenCalled(); second.release(); first.release(); }); it("reports operational failures through the contextual error channel", () => { const failure = new Error("model hydration failed"); const onError = vi.fn(); const lease = acquireToolcraftSourceAssetOwner({ createCoordinator: () => createTestSourceAssetCoordinator(), onError, store: createTestSourceAssetStore(), }); lease.reportError(failure); expect(onError).toHaveBeenCalledWith(failure); lease.release(); }); it("preserves cleanup and contextual reporter failures", async () => { const cleanupFailure = new Error("cleanup failed"); const reporterFailure = new Error("reporter failed"); const reportError = vi.fn(); vi.stubGlobal("reportError", reportError); const lease = acquireToolcraftSourceAssetOwner({ createCoordinator: () => createTestSourceAssetCoordinator({ dispose: vi.fn(async () => Promise.reject(cleanupFailure)), }), onError: () => { throw reporterFailure; }, store: createTestSourceAssetStore(), }); lease.release(); await vi.waitFor(() => expect(reportError).toHaveBeenCalledTimes(1)); const reported = reportError.mock.calls[0]?.[0]; expect(reported).toBeInstanceOf(AggregateError); expect((reported as AggregateError).errors).toEqual([ cleanupFailure, reporterFailure, ]); }); });