227 lines · 7.9 KB · TypeScript
RawDownload
import {  EnvironmentId,  type ServerConfig,  type ServerConfigStreamEvent,  type ServerLifecycleWelcomePayload,  WS_METHODS,} from "@t3tools/contracts";import { describe, expect, it } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Option from "effect/Option";import * as Queue from "effect/Queue";import * as Stream from "effect/Stream";import * as SubscriptionRef from "effect/SubscriptionRef"; import {  AVAILABLE_CONNECTION_STATE,  PrimaryConnectionTarget,  type PreparedConnection,} from "../connection/model.ts";import * as EnvironmentSupervisor from "../connection/supervisor.ts";import * as Persistence from "../platform/persistence.ts";import type { WsRpcProtocolClient } from "../rpc/protocol.ts";import type { RpcSession } from "../rpc/session.ts";import {  applyServerConfigProjection,  makeEnvironmentServerConfigState,  projectServerWelcome,  resolveServerConfigValue,} from "./server.ts"; const CONFIG = {  availableEditors: [],  issues: [],  keybindings: {},  keybindingsConfigPath: null,  observability: null,  providers: [],  settings: {},} as unknown as ServerConfig; const snapshotEvent = (config: ServerConfig): ServerConfigStreamEvent => ({  version: 1,  type: "snapshot",  config,}); const TARGET = new PrimaryConnectionTarget({  environmentId: EnvironmentId.make("environment-1"),  label: "Test environment",  httpBaseUrl: "https://environment.example.test",  wsBaseUrl: "wss://environment.example.test",}); function session(client: WsRpcProtocolClient): RpcSession {  return {    client,    initialConfig: Effect.succeed(CONFIG),    ready: Effect.void,    probe: Effect.void,    closed: Effect.never,  };} describe("server state projection", () => {  it("applies every config category to the projected snapshot", () => {    const snapshot = applyServerConfigProjection(Option.none(), {      version: 1,      type: "snapshot",      config: CONFIG,    });    const settings = { ...CONFIG.settings };    const projected = applyServerConfigProjection(snapshot, {      version: 1,      type: "settingsUpdated",      payload: { settings },    });     const result = Option.getOrThrow(projected);    expect(result.config.settings).toBe(settings);    expect(result.latestEvent.type).toBe("settingsUpdated");  });   it("retains welcome when a ready event follows in the same stream chunk", () => {    const welcome = {      environment: {} as ServerLifecycleWelcomePayload["environment"],      cwd: "/repo",      projectName: "repo",    } as ServerLifecycleWelcomePayload;    const [afterWelcome] = projectServerWelcome(Option.none(), {      type: "welcome",      payload: welcome,    });    const [afterReady, emitted] = projectServerWelcome(afterWelcome, {      type: "ready",      payload: {},    });     expect(Option.getOrThrow(afterReady)).toBe(welcome);    expect(emitted).toEqual([]);  });   it("prefers an active session config over cache until a live event arrives", () => {    const cached = { ...CONFIG, settings: { source: "cache" } } as unknown as ServerConfig;    const initial = { ...CONFIG, settings: { source: "session" } } as unknown as ServerConfig;    const live = { ...CONFIG, settings: { source: "live" } } as unknown as ServerConfig;     expect(      resolveServerConfigValue(        {          config: cached,          latestEvent: snapshotEvent(cached),          source: "cache",        },        initial,      ),    ).toBe(initial);    expect(      resolveServerConfigValue(        {          config: live,          latestEvent: snapshotEvent(live),          source: "live",        },        initial,      ),    ).toBe(live);  });   it.effect("starts from cached configuration and persists the live projection", () =>    Effect.gen(function* () {      const events = yield* Queue.unbounded<ServerConfigStreamEvent>();      const client = {        [WS_METHODS.subscribeServerConfig]: () => Stream.fromQueue(events),      } as unknown as WsRpcProtocolClient;      const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({        target: TARGET,        state: yield* SubscriptionRef.make(AVAILABLE_CONNECTION_STATE),        session: yield* SubscriptionRef.make(Option.some(session(client))),        prepared: yield* SubscriptionRef.make(Option.none<PreparedConnection>()),        connect: Effect.void,        disconnect: Effect.void,        retryNow: Effect.void,      } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]);      const savedConfigs = yield* Queue.unbounded<ServerConfig>();      const cache = Persistence.EnvironmentCacheStore.of({        loadShell: () => Effect.succeed(Option.none()),        saveShell: () => Effect.void,        loadThread: () => Effect.succeed(Option.none()),        saveThread: () => Effect.void,        removeThread: () => Effect.void,        loadServerConfig: () => Effect.succeed(Option.some(CONFIG)),        saveServerConfig: (_environmentId, config) => Queue.offer(savedConfigs, config),        loadVcsRefs: () => Effect.succeed(Option.none()),        saveVcsRefs: () => Effect.void,        clear: () => Effect.void,      });       yield* Effect.scoped(        Effect.gen(function* () {          const state = yield* makeEnvironmentServerConfigState().pipe(            Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor),            Effect.provideService(Persistence.EnvironmentCacheStore, cache),          );          expect(Option.getOrThrow(yield* SubscriptionRef.get(state)).config).toBe(CONFIG);           const providers: ServerConfig["providers"] = [];          yield* Queue.offer(events, {            version: 1,            type: "providerStatuses",            payload: { providers },          });          const projected = yield* SubscriptionRef.changes(state).pipe(            Stream.filter((value) =>              Option.match(value, {                onNone: () => false,                onSome: (projection) => projection.latestEvent.type === "providerStatuses",              }),            ),            Stream.runHead,          );          expect(Option.getOrThrow(Option.getOrThrow(projected)).config.providers).toBe(providers);        }),      );       expect((yield* Queue.take(savedConfigs)).providers).toEqual([]);    }),  );   it.effect("does not rewrite cached configuration when no live update arrives", () =>    Effect.gen(function* () {      const client = {        [WS_METHODS.subscribeServerConfig]: () => Stream.empty,      } as unknown as WsRpcProtocolClient;      const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({        target: TARGET,        state: yield* SubscriptionRef.make(AVAILABLE_CONNECTION_STATE),        session: yield* SubscriptionRef.make(Option.some(session(client))),        prepared: yield* SubscriptionRef.make(Option.none<PreparedConnection>()),        connect: Effect.void,        disconnect: Effect.void,        retryNow: Effect.void,      } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]);      const savedConfigs = yield* Queue.unbounded<ServerConfig>();      const cache = Persistence.EnvironmentCacheStore.of({        loadShell: () => Effect.succeed(Option.none()),        saveShell: () => Effect.void,        loadThread: () => Effect.succeed(Option.none()),        saveThread: () => Effect.void,        removeThread: () => Effect.void,        loadServerConfig: () => Effect.succeed(Option.some(CONFIG)),        saveServerConfig: (_environmentId, config) => Queue.offer(savedConfigs, config),        loadVcsRefs: () => Effect.succeed(Option.none()),        saveVcsRefs: () => Effect.void,        clear: () => Effect.void,      });       yield* Effect.scoped(        makeEnvironmentServerConfigState().pipe(          Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor),          Effect.provideService(Persistence.EnvironmentCacheStore, cache),        ),      );       expect(yield* Queue.poll(savedConfigs)).toEqual(Option.none());    }),  );});