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 { 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 ElectronDialog from "../electron/ElectronDialog.ts";import * as DesktopWindow from "../window/DesktopWindow.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<DesktopBackendStartConfig>()), snapshot: Effect.succeed(snapshot), waitForReady: (_timeout: Duration.Duration) => Effect.succeed(false), };} function makePoolLayer( labelRef: Ref.Ref<string>,): Layer.Layer<DesktopBackendPool.DesktopBackendPool> { 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({ writeSessionBoundary: () => Effect.void, writeOutputChunk: () => Effect.void, } satisfies DesktopObservability.DesktopBackendOutputLogShape), } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]), 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(), 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"), 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)"); }), ), );});
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 { 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 ElectronDialog from "../electron/ElectronDialog.ts";
import * as DesktopWindow from "../window/DesktopWindow.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<DesktopBackendStartConfig>()),
snapshot: Effect.succeed(snapshot),
waitForReady: (_timeout: Duration.Duration) => Effect.succeed(false),
};
}
function makePoolLayer(
labelRef: Ref.Ref<string>,
): Layer.Layer<DesktopBackendPool.DesktopBackendPool> {
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({
writeSessionBoundary: () => Effect.void,
writeOutputChunk: () => Effect.void,
} satisfies DesktopObservability.DesktopBackendOutputLogShape),
} satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]),
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(),
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"),
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)");
}),
),
);
});