import { describe, expect, it } from "@effect/vitest"; import { type AssetCreateUrlResult, AssetWorkspaceAssetNotFoundError, AssetWorkspaceAssetInspectionError, AssetWorkspaceContextNotFoundError, EnvironmentAuthorizationError, EnvironmentId, ThreadId, WS_METHODS, } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Stream from "effect/Stream"; import * as SubscriptionRef from "effect/SubscriptionRef"; import * as Option from "effect/Option"; import * as Layer from "effect/Layer"; import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; import { EnvironmentRegistry } from "../connection/registry.ts"; import { AVAILABLE_CONNECTION_STATE, PrimaryConnectionTarget, type PreparedConnection, type SupervisorConnectionState, } from "../connection/model.ts"; import { EnvironmentSupervisor } from "../connection/supervisor.ts"; import type { RpcSession } from "../rpc/session.ts"; import type { WsRpcProtocolClient } from "../rpc/protocol.ts"; import { createProjectFaviconCache } from "../projectFaviconCache.ts"; import { createAssetEnvironmentAtoms, createProjectFaviconUrlAtomFamily, InvalidAssetCollectionKeyError, parseAssetCollectionKey, } from "./assets.ts"; describe("asset collection keys", () => { it("preserves malformed JSON and its native cause", () => { const key = "not-json"; let error: unknown; try { parseAssetCollectionKey(key); } catch (cause) { error = cause; } expect(error).toBeInstanceOf(InvalidAssetCollectionKeyError); expect(error).toMatchObject({ key, cause: expect.any(SyntaxError) }); }); it("rejects invalid asset collection shapes", () => { const key = JSON.stringify(["environment-1", [{ _tag: "unknown" }]]); expect(() => parseAssetCollectionKey(key)).toThrowError(InvalidAssetCollectionKeyError); }); }); describe("createAssetEnvironmentAtoms", () => { for (const scenario of [ { name: "missing video", path: "/tmp/clip.mp4", fallback: true }, { name: "literal filename characters", path: "/tmp/frame#one?two.png", fallback: true }, { name: "windows path", path: "C:\\Users\\demo\\clip.mp4", fallback: true }, { name: "inspection failure", path: "/tmp/clip.mp4", error: "inspection", fallback: true }, { name: "foreign thread", path: "/tmp/clip.mp4", error: "context", fallback: true }, { name: "remote success", path: "/tmp/clip.mp4", success: true }, { name: "relative path", path: "clip.mp4" }, { name: "no primary", path: "/tmp/clip.mp4", primary: "none" }, { name: "primary reconnect", path: "/tmp/frame.png", primary: "reconnecting" }, { name: "same environment", path: "/tmp/clip.mp4", primary: "same" }, { name: "non-media", path: "/tmp/report.html" }, { name: "authorization failure", path: "/tmp/clip.mp4", error: "auth" }, ]) { it.effect(`uses the correct environment for ${scenario.name}`, () => Effect.gen(function* () { const remoteId = EnvironmentId.make("remote"); const localId = EnvironmentId.make("local"); const resource = { _tag: "media-file" as const, threadId: ThreadId.make("foreign-thread"), path: scenario.path, }; const error = scenario.error === "auth" ? new EnvironmentAuthorizationError({ message: "denied", requiredScope: "orchestration:read", }) : scenario.error === "inspection" ? new AssetWorkspaceAssetInspectionError({ resource, cause: new Error("unreadable") }) : scenario.error === "context" ? new AssetWorkspaceContextNotFoundError({ resource }) : new AssetWorkspaceAssetNotFoundError({ resource }); const calls: EnvironmentId[] = []; const supervisors = new Map(); for (const environmentId of [remoteId, localId]) { const client = { [WS_METHODS.assetsCreateUrl]: () => { calls.push(environmentId); return environmentId === remoteId && !scenario.success ? Effect.fail(error) : Effect.succeed({ relativeUrl: `/api/assets/${environmentId}/media`, expiresAt: 999999, }); }, } as unknown as WsRpcProtocolClient; const session = { client } as RpcSession; supervisors.set( environmentId, EnvironmentSupervisor.of({ target: new PrimaryConnectionTarget({ environmentId, label: environmentId, httpBaseUrl: `https://${environmentId}.test`, wsBaseUrl: `wss://${environmentId}.test`, }), state: yield* SubscriptionRef.make({ ...AVAILABLE_CONNECTION_STATE, phase: "connected" as const, }), session: yield* SubscriptionRef.make(Option.some(session)), prepared: yield* SubscriptionRef.make(Option.none()), connect: Effect.void, disconnect: Effect.void, retryNow: Effect.void, }), ); } const environments = EnvironmentRegistry.of({ run: (id, effect) => Effect.provideService(effect, EnvironmentSupervisor, supervisors.get(id)!), followStream: (id, stream) => Stream.provideService(stream, EnvironmentSupervisor, supervisors.get(id)!), } as EnvironmentRegistry["Service"]); const registry = AtomRegistry.make(); yield* Effect.addFinalizer(() => Effect.sync(() => registry.dispose())); const localTarget = { environmentId: scenario.primary === "same" ? remoteId : localId, httpBaseUrl: "https://local.test", }; const localEnvironment = Atom.make( scenario.primary === "none" || scenario.primary === "reconnecting" ? null : localTarget, ); const assets = createAssetEnvironmentAtoms( Atom.runtime(Layer.succeed(EnvironmentRegistry, environments)), localEnvironment, ); const query = assets.createUrl({ environmentId: remoteId, input: { resource } }); const result = AtomRegistry.getResult(registry, query, { suspendOnWaiting: true }); if (scenario.fallback || scenario.success) { expect((yield* result).relativeUrl).toBe( scenario.fallback ? "https://local.test/api/assets/local/media" : "/api/assets/remote/media", ); } else { expect(yield* Effect.flip(result)).toEqual(error); } expect(calls).toEqual(scenario.fallback ? [remoteId, localId] : [remoteId]); if (scenario.primary === "reconnecting") { registry.set(localEnvironment, localTarget); expect((yield* result).relativeUrl).toBe("https://local.test/api/assets/local/media"); expect(calls).toEqual([remoteId, remoteId, localId]); } }).pipe(Effect.scoped), ); } it("keys asset URL queries by environment and resource", () => { const runtime = Atom.runtime(Layer.empty) as unknown as Atom.AtomRuntime< EnvironmentRegistry, never >; const assets = createAssetEnvironmentAtoms(runtime); const environmentId = EnvironmentId.make("environment-1"); const originalTarget = { environmentId, input: { resource: { _tag: "project-favicon" as const, cwd: "/repo/original", }, }, }; expect(assets.createUrl(originalTarget)).toBe( assets.createUrl({ environmentId, input: { resource: { _tag: "project-favicon", cwd: "/repo/original", }, }, }), ); expect( assets.createUrl({ environmentId, input: { resource: { _tag: "project-favicon", cwd: "/repo/next", }, }, }), ).not.toBe(assets.createUrl(originalTarget)); expect( assets.createUrl({ environmentId, input: { resource: { _tag: "project-favicon", cwd: "/repo/original", path: "brand/icon.svg", }, }, }), ).not.toBe(assets.createUrl(originalTarget)); expect( assets.createUrl({ environmentId: EnvironmentId.make("environment-2"), input: originalTarget.input, }), ).not.toBe(assets.createUrl(originalTarget)); }); it("keys collections while preserving independent resource queries", () => { const runtime = Atom.runtime(Layer.empty) as unknown as Atom.AtomRuntime< EnvironmentRegistry, never >; const assets = createAssetEnvironmentAtoms(runtime); const environmentId = EnvironmentId.make("environment-1"); const resources = [ { _tag: "attachment" as const, attachmentId: "attachment-1" }, { _tag: "attachment" as const, attachmentId: "attachment-2" }, ]; expect(assets.createUrls({ environmentId, resources })).toBe( assets.createUrls({ environmentId, resources: resources.map((resource) => ({ ...resource })), }), ); expect( assets.createUrls({ environmentId, resources: [...resources].toReversed(), }), ).not.toBe(assets.createUrls({ environmentId, resources })); }); }); describe("project favicon URL cache", () => { it("renders a persisted thumbnail immediately in a fresh registry and refreshes it remotely", async () => { const image = "data:image/png;base64,aWNvbg=="; const replacement = "data:image/png;base64,bmV3"; const records = new Map(); const storage = { list: async () => [...records.values()], put: async (key: string, entry: unknown) => { records.set(key, entry); }, remove: async (key: string) => { records.delete(key); }, }; const target = { environmentId: EnvironmentId.make("remote"), cwd: "/workspace" }; const previousCache = createProjectFaviconCache({ storage, load: async () => image }); await previousCache.resolve( target, "https://remote.test/api/assets/old/v1-icon.png", new AbortController().signal, ); await previousCache.flush(); const cache = createProjectFaviconCache({ storage, load: async () => replacement }); await cache.hydrate(); const registry = AtomRegistry.make(); const result = Atom.make>( AsyncResult.initial(), ); const connection = Atom.make>(Option.none()); const favicon = createProjectFaviconUrlAtomFamily({ createUrl: () => result, preparedConnection: () => connection, imageCache: cache, })(target); const unmount = registry.mount(favicon); try { expect(registry.get(favicon)).toBe(image); let unsubscribe = () => {}; const refreshed = new Promise((resolve) => { unsubscribe = registry.subscribe(favicon, (value) => { if (value === replacement) resolve(); }); }); registry.set(connection, Option.some({ httpBaseUrl: "https://remote.test" })); registry.set( result, AsyncResult.success({ relativeUrl: "/api/assets/new/v2-icon.png", expiresAt: 4_000_000_000_000, }), ); expect(registry.get(favicon)).toBe(image); await refreshed; unsubscribe(); expect(registry.get(favicon)).toBe(replacement); registry.set(connection, Option.none()); registry.set(result, AsyncResult.failure(Cause.die("offline"))); expect(registry.get(favicon)).toBe(replacement); } finally { unmount(); registry.dispose(); } }); it("retains icons across outages and remounts, then accepts refreshed and missing icons", () => { const registry = AtomRegistry.make(); const result = Atom.make>( AsyncResult.initial(), ); const connection = Atom.make(Option.some({ httpBaseUrl: "https://remote.test" })); const favicon = createProjectFaviconUrlAtomFamily({ createUrl: () => result, preparedConnection: () => connection, })({ environmentId: EnvironmentId.make("remote"), cwd: "/workspace" }); let unmount = registry.mount(favicon); try { expect(registry.get(favicon)).toBeNull(); registry.set( result, AsyncResult.success({ expiresAt: 4_000_000_000_000, relativeUrl: "/api/assets/token-a/icon.svg", }), ); expect(registry.get(favicon)).toBe("https://remote.test/api/assets/token-a/icon.svg"); registry.set(connection, Option.none()); registry.set(result, AsyncResult.failure(Cause.die("disconnected"))); expect(registry.get(favicon)).toBe("https://remote.test/api/assets/token-a/icon.svg"); unmount(); unmount = registry.mount(favicon); expect(registry.get(favicon)).toBe("https://remote.test/api/assets/token-a/icon.svg"); registry.set(result, AsyncResult.initial()); registry.set(connection, Option.some({ httpBaseUrl: "https://reconnected.test" })); expect(registry.get(favicon)).toBe("https://remote.test/api/assets/token-a/icon.svg"); registry.set( result, AsyncResult.success({ expiresAt: 4_000_000_000_000, relativeUrl: "/api/assets/token-b/icon.svg", }), ); expect(registry.get(favicon)).toBe("https://reconnected.test/api/assets/token-b/icon.svg"); registry.set( result, AsyncResult.success({ expiresAt: 4_000_000_000_000, relativeUrl: "/api/assets/token-c/project-favicon-missing", }), ); expect(registry.get(favicon)).toBe( "https://reconnected.test/api/assets/token-c/project-favicon-missing", ); registry.set(connection, Option.none()); expect(registry.get(favicon)).toBe( "https://reconnected.test/api/assets/token-c/project-favicon-missing", ); } finally { unmount(); registry.dispose(); } }); it("does not reuse another environment, workspace, or selected icon's cached URL", () => { const registry = AtomRegistry.make(); const result = Atom.make>( AsyncResult.success({ expiresAt: 4_000_000_000_000, relativeUrl: "/api/assets/token/icon.svg", }), ); const favicon = createProjectFaviconUrlAtomFamily({ createUrl: () => result, preparedConnection: () => Atom.make(Option.some({ httpBaseUrl: "https://remote.test" })), }); const target = { environmentId: EnvironmentId.make("remote"), cwd: "/workspace" }; const unmount = registry.mount(favicon(target)); try { expect(registry.get(favicon(target))).toBe("https://remote.test/api/assets/token/icon.svg"); registry.set(result, AsyncResult.failure(Cause.die("disconnected"))); expect( registry.get(favicon({ ...target, environmentId: EnvironmentId.make("other") })), ).toBeNull(); expect(registry.get(favicon({ ...target, cwd: "/other" }))).toBeNull(); expect(registry.get(favicon({ ...target, faviconPath: "brand.svg" }))).toBeNull(); expect(registry.get(favicon({ ...target, faviconPath: null }))).toBe( "https://remote.test/api/assets/token/icon.svg", ); } finally { unmount(); registry.dispose(); } }); });