import { assert, describe, it } from "@effect/vitest"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; import * as Stream from "effect/Stream"; import { HttpClient } from "effect/unstable/http"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as DesktopObservability from "../app/DesktopObservability.ts"; import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts"; import * as DesktopTelemetryPublisher from "../telemetry/DesktopTelemetryPublisher.ts"; import * as ElectronDialog from "../electron/ElectronDialog.ts"; import * as DesktopWindow from "../window/DesktopWindow.ts"; import * as DesktopWslEnvironment from "../wsl/DesktopWslEnvironment.ts"; import * as DesktopBackendConfiguration from "./DesktopBackendConfiguration.ts"; import * as DesktopBackendPool from "./DesktopBackendPool.ts"; import type { DesktopBackendSnapshot, DesktopBackendStartConfig } from "./DesktopBackendManager.ts"; function makeStubInstance( id: DesktopBackendPool.BackendInstanceId, label: string, ): DesktopBackendPool.DesktopBackendInstance { const snapshot: DesktopBackendSnapshot = { desiredRunning: false, ready: false, activePid: Option.none(), restartAttempt: 0, restartScheduled: false, }; return { id, label: Effect.succeed(label), start: Effect.void, stop: () => Effect.void, currentConfig: Effect.succeed(Option.none()), snapshot: Effect.succeed(snapshot), waitForReady: (_timeout: Duration.Duration) => Effect.succeed(false), }; } function makePoolLayer( labelRef: Ref.Ref, ): Layer.Layer { return DesktopBackendPool.layer.pipe( Layer.provideMerge( Layer.mergeAll( FileSystem.layerNoop({}), Layer.succeed( ChildProcessSpawner.ChildProcessSpawner, ChildProcessSpawner.make(() => Effect.die("unexpected child process spawn")), ), Layer.succeed( HttpClient.HttpClient, HttpClient.make(() => Effect.die("unexpected HTTP request")), ), Layer.succeed(DesktopObservability.DesktopBackendOutputLogFactory, { forInstance: () => Effect.succeed({ beginSession: () => Effect.void, writeOutputChunk: () => Effect.void, persistFailureSnapshot: () => Effect.void, persistFailure: () => Effect.void, discardSession: Effect.void, } satisfies DesktopObservability.DesktopBackendOutputLogShape), } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]), Layer.succeed(DesktopTelemetryPublisher.DesktopTelemetryPublisher, { latest: Effect.succeed(Option.none()), changes: Stream.empty, encoded: Stream.empty, handleControl: () => Effect.void, handleControlForSource: () => Effect.void, removeControlSource: () => Effect.void, publishUpdateReport: () => Effect.void, updateRequests: Stream.empty, updateCommits: Stream.empty, updateCancellations: Stream.empty, }), Layer.succeed(DesktopBackendConfiguration.DesktopBackendConfiguration, { resolvePrimary: Effect.die("unexpected primary config resolve"), resolvePrimaryLabel: Ref.get(labelRef), resolveWsl: () => Effect.die("unexpected WSL config resolve"), } satisfies DesktopBackendConfiguration.DesktopBackendConfiguration["Service"]), DesktopAppSettings.layerTest(), DesktopWslEnvironment.layerTest(), ElectronDialog.layer, Layer.succeed(DesktopWindow.DesktopWindow, { createMain: Effect.die("unexpected window create"), ensureMain: Effect.die("unexpected window ensure"), revealOrCreateMain: Effect.die("unexpected window reveal"), activate: Effect.die("unexpected window activate"), createMainIfBackendReady: Effect.die("unexpected window create"), showConnectingSplash: Effect.void, handleBackendReady: () => Effect.void, handleBackendNotReady: Effect.void, flushMainWindowBounds: Effect.void, dispatchMenuAction: () => Effect.die("unexpected menu action"), zoomMain: () => Effect.die("unexpected zoom"), syncAppearance: Effect.void, } satisfies DesktopWindow.DesktopWindow["Service"]), ), ), ); } describe("DesktopBackendPool", () => { it.effect("layerTest exposes registered instances by id", () => Effect.gen(function* () { const pool = yield* DesktopBackendPool.DesktopBackendPool; const fetchedPrimary = yield* pool.get(DesktopBackendPool.PRIMARY_INSTANCE_ID); const fetchedWsl = yield* pool.get(DesktopBackendPool.BackendInstanceId("wsl:ubuntu")); const fetchedMissing = yield* pool.get(DesktopBackendPool.BackendInstanceId("missing")); const all = yield* pool.list; const resolvedPrimary = yield* pool.primary; assert.equal(yield* Option.getOrThrow(fetchedPrimary).label, "Windows"); assert.equal(yield* Option.getOrThrow(fetchedWsl).label, "WSL (Ubuntu)"); assert.isTrue(Option.isNone(fetchedMissing)); assert.lengthOf(all, 2); // First instance becomes primary in layerTest so single-instance // stubs don't have to wire an explicit primary. assert.equal(resolvedPrimary.id, DesktopBackendPool.PRIMARY_INSTANCE_ID); }).pipe( Effect.provide( DesktopBackendPool.layerTest([ makeStubInstance(DesktopBackendPool.PRIMARY_INSTANCE_ID, "Windows"), makeStubInstance(DesktopBackendPool.BackendInstanceId("wsl:ubuntu"), "WSL (Ubuntu)"), ]), ), ), ); it.effect("layerTest dies when no instances are supplied", () => Effect.exit( Effect.gen(function* () { yield* DesktopBackendPool.DesktopBackendPool; }).pipe(Effect.provide(DesktopBackendPool.layerTest([]))), ).pipe(Effect.map((exit) => assert.equal(exit._tag, "Failure"))), ); it.effect("resolves the primary label lazily after pool layer construction", () => Effect.scoped( Effect.gen(function* () { const labelRef = yield* Ref.make("Windows"); const pool = yield* DesktopBackendPool.DesktopBackendPool.pipe( Effect.provide(makePoolLayer(labelRef)), ); const primary = yield* pool.primary; yield* Ref.set(labelRef, "WSL (Ubuntu)"); assert.equal(yield* primary.label, "WSL (Ubuntu)"); }), ), ); });