412 lines · 15.5 KB · TypeScript
RawDownload
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<EnvironmentId, EnvironmentSupervisor["Service"]>();        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<SupervisorConnectionState>({                ...AVAILABLE_CONNECTION_STATE,                phase: "connected" as const,              }),              session: yield* SubscriptionRef.make(Option.some(session)),              prepared: yield* SubscriptionRef.make(Option.none<PreparedConnection>()),              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<typeof localTarget | null>(          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<string, unknown>();    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.AsyncResult<AssetCreateUrlResult, unknown>>(      AsyncResult.initial(),    );    const connection = Atom.make<Option.Option<{ httpBaseUrl: string }>>(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<void>((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.AsyncResult<AssetCreateUrlResult, unknown>>(      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.AsyncResult<AssetCreateUrlResult, unknown>>(      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();    }  });});