import type { IrManifestEntry } from "@suaveplan/audio-ir-core"; import { createIrRegistry } from "@suaveplan/audio-ir-core"; import { createStorageUriResolver, type UriPlugin, } from "@suaveplan/storage-uri"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { ImpulseResponseCache } from "../cache/cache.js"; import { type AudioDecoder, createIrLoader } from "../loader/loader.js"; import { runPreload } from "../loader/preload.js"; import { resetInstruments } from "../otel/otel.js"; import { AUDIO_IR_LOADER_CODES, AudioIrLoaderError, type AudioIrLoaderErrorCode, } from "./error-codes.js"; const ENTRY: IrManifestEntry = Object.freeze({ id: "club-club1", family: "room", uri: "bundle://x/club1.wav", channels: 1, sampleRate: 44100, gainDb: 0, license: "MIT", source: "demo", }) as IrManifestEntry; const FLAC_ENTRY: IrManifestEntry = Object.freeze({ ...ENTRY, id: "club-club2", uri: "bundle://x/club2.flac", }) as IrManifestEntry; function buildResolver(reads: Record): { resolver: ReturnType; plugin: UriPlugin; } { const plugin: UriPlugin = { scheme: "bundle", adapter: { async get(key: string) { return reads[key]; }, async set() { return; }, async delete() { return true; }, async exists(key) { return key in reads; }, async list() { return { keys: [], total: 0, hasMore: false }; }, async clear() { return; }, }, parse(uri: string) { const noScheme = uri.replace(/^bundle:\/\//, ""); return { key: noScheme }; }, }; return { resolver: createStorageUriResolver([plugin]), plugin }; } const noopWavBytes = new Uint8Array([1, 2, 3]); function decoderThrowing(code: string): AudioDecoder { return async () => { const err = new Error(`fixture decoder threw ${code}`); throw err; }; } async function exerciseDecoderUnavailable(): Promise< AudioIrLoaderError | undefined > { resetInstruments(); const registry = createIrRegistry(); registry.register(FLAC_ENTRY, { manifestSlug: "club", manifestFamily: "room", }); const { resolver } = buildResolver({ "x/club2.flac": noopWavBytes }); const loader = createIrLoader({ registry, resolver, decoders: { wav: decoderThrowing("nope") }, }); try { await loader.load("club-club2"); } catch (err) { if (err instanceof AudioIrLoaderError) return err; } return undefined; } async function exerciseManifestMismatch(): Promise< AudioIrLoaderError | undefined > { resetInstruments(); const registry = createIrRegistry(); registry.register(ENTRY, { manifestSlug: "club", manifestFamily: "room", }); const { resolver } = buildResolver({ "x/club1.wav": noopWavBytes }); const decoder: AudioDecoder = async () => ({ channelData: [new Float32Array([0.1])], channels: 1, sampleRate: 22050, }); const loader = createIrLoader({ registry, resolver, decoders: { wav: decoder }, }); try { await loader.load("club-club1"); } catch (err) { if (err instanceof AudioIrLoaderError) return err; } return undefined; } async function exerciseAborted(): Promise { resetInstruments(); const registry = createIrRegistry(); registry.register(ENTRY, { manifestSlug: "club", manifestFamily: "room", }); const { resolver } = buildResolver({ "x/club1.wav": noopWavBytes }); const decoder: AudioDecoder = async () => ({ channelData: [new Float32Array([0])], channels: 1, sampleRate: 44100, }); const loader = createIrLoader({ registry, resolver, decoders: { wav: decoder }, }); const controller = new AbortController(); controller.abort(new Error("preflight")); try { await loader.load("club-club1", { signal: controller.signal }); } catch (err) { if (err instanceof AudioIrLoaderError) return err; } return undefined; } async function exerciseCacheOverflow(): Promise< AudioIrLoaderError | undefined > { const cache = new ImpulseResponseCache(64); const big = Object.freeze({ id: "big", channelData: Object.freeze([new Float32Array(1024)]), channels: 1, sampleRate: 44100, lengthFrames: 1024, durationSec: 1024 / 44100, gainDb: 0, family: "room" as const, manifest: ENTRY, }); try { cache.put("big", big as unknown as Parameters[1]); } catch (err) { if (err instanceof AudioIrLoaderError) return err; } return undefined; } async function exerciseDecodeFailed(): Promise { resetInstruments(); const registry = createIrRegistry(); registry.register(ENTRY, { manifestSlug: "club", manifestFamily: "room", }); const { resolver } = buildResolver({ "x/club1.wav": noopWavBytes }); const decoder: AudioDecoder = async () => ({ channelData: [new Float32Array([0])], channels: 99, sampleRate: 44100, }); const loader = createIrLoader({ registry, resolver, decoders: { wav: decoder }, }); try { await runPreload( (id, opts) => loader.load(id, opts), ["club-club1"], undefined ); } catch (err) { if (err instanceof AudioIrLoaderError) return err; } return undefined; } async function exerciseResolveFailed(): Promise< AudioIrLoaderError | undefined > { resetInstruments(); const registry = createIrRegistry(); registry.register(ENTRY, { manifestSlug: "club", manifestFamily: "room", }); const { resolver } = buildResolver({ "x/club1.wav": noopWavBytes }); const decoder: AudioDecoder = async () => ({ channelData: [new Float32Array([0])], channels: 1, sampleRate: 44100, }); const loader = createIrLoader({ registry, resolver, decoders: { wav: decoder }, }); // Build a fake load fn that throws a non-SuaveplanError to trigger // the RESOLVE_FAILED wrapper inside runPreload. const badLoader = async () => { throw new TypeError("plain error"); }; try { await runPreload( badLoader as unknown as (id: string) => Promise, ["x"], undefined ); } catch (err) { // top-level err is DECODE_FAILED whose `errors[0].error` is the RESOLVE_FAILED if (err instanceof AudioIrLoaderError && err.errors !== undefined) { const first = err.errors[0]?.error; if (first instanceof AudioIrLoaderError) return first; } } // touch loader to avoid unused warning void loader; return undefined; } function siteFor( code: AudioIrLoaderErrorCode ): () => Promise { if (code === AUDIO_IR_LOADER_CODES.DECODER_UNAVAILABLE) return exerciseDecoderUnavailable; if (code === AUDIO_IR_LOADER_CODES.MANIFEST_MISMATCH) return exerciseManifestMismatch; if (code === AUDIO_IR_LOADER_CODES.ABORTED) return exerciseAborted; if (code === AUDIO_IR_LOADER_CODES.CACHE_OVERFLOW) return exerciseCacheOverflow; if (code === AUDIO_IR_LOADER_CODES.DECODE_FAILED) return exerciseDecodeFailed; if (code === AUDIO_IR_LOADER_CODES.RESOLVE_FAILED) return exerciseResolveFailed; return async () => undefined; } describe("AUDIO_IR_LOADER_CODES exhaustion", () => { it("every AUDIO_IR_LOADER code reachable from a real call site", { timeout: 30000, }, async () => { const all = Object.values(AUDIO_IR_LOADER_CODES); expect(all.length).toBe(6); for (const code of all) { const err = await siteFor(code)(); expect(err, `no real call site exercises ${code}`).toBeDefined(); expect(err?.code).toBe(code); } }); it("registry exposes exactly six codes", { timeout: 5000 }, () => { const names = Object.keys(AUDIO_IR_LOADER_CODES).sort(); expect(names).toEqual([ "ABORTED", "CACHE_OVERFLOW", "DECODER_UNAVAILABLE", "DECODE_FAILED", "MANIFEST_MISMATCH", "RESOLVE_FAILED", ]); }); it("AUDIO_IR_LOADER_CODES registry is frozen", { timeout: 5000 }, () => { expect(Object.isFrozen(AUDIO_IR_LOADER_CODES)).toBe(true); }); it("AudioIrLoaderError surfaces typed discriminants", { timeout: 5000, }, () => { const err = new AudioIrLoaderError( AUDIO_IR_LOADER_CODES.MANIFEST_MISMATCH, "x", { id: "club-a", field: "channels", manifestValue: 2, decodedValue: 1, } ); expect(err.name).toBe("AudioIrLoaderError"); expect(err.id).toBe("club-a"); expect(err.field).toBe("channels"); expect(err.manifestValue).toBe(2); expect(err.decodedValue).toBe(1); }); it("AudioIrLoaderError with no options leaves discriminants undefined", { timeout: 5000, }, () => { const err = new AudioIrLoaderError(AUDIO_IR_LOADER_CODES.ABORTED, "x"); expect(err.id).toBeUndefined(); expect(err.extension).toBeUndefined(); expect(err.field).toBeUndefined(); expect(err.manifestValue).toBeUndefined(); expect(err.decodedValue).toBeUndefined(); expect(err.maxBytes).toBeUndefined(); expect(err.decodedBytes).toBeUndefined(); expect(err.errors).toBeUndefined(); }); });