126 lines · 5.1 KB · TypeScript
RawDownload
import { expect, it } from "@effect/vitest";import { ThreadId } from "@t3tools/contracts";import * as Deferred from "effect/Deferred";import * as Fiber from "effect/Fiber";import * as Effect from "effect/Effect";import { HttpClient, HttpClientResponse } from "effect/unstable/http";import { ServerSettingsService } from "../serverSettings.ts";import { DeviceHostError, DeviceHost } from "./DeviceHost.ts";import { makeWithHosts } from "./DeviceService.ts"; it.effect("keeps hosts independent when serials collide and another host fails", () =>  Effect.gen(function* () {    const host = (id: string, failed = false): DeviceHost["Service"] => {      const ready = {        nodePath: process.execPath,        hub: { origin: `http://${id}` },        agentDevice: { baseUrl: `http://${id}`, token: "test", entryPath: "/agent-device" },        run: () => Effect.succeed({ stdout: "", stderr: "", code: 0 }),        helpers: { serveSimAxSettings: null, serveSimCli: null },      };      return {        id,        summary: Effect.succeed({          id,          label: id,          kind: id === "b" ? "ssh" : "local",          hubInstalled: true,          agentDeviceInstalled: true,          platforms: id === "b" ? [] : [{ platform: "android", available: true }],        }),        platformAvailability: (platform) => Effect.succeed({ platform, available: true }),        ensureReady: () =>          failed            ? Effect.fail(                new DeviceHostError({ hostId: id, step: "connect", cause: new Error("offline") }),              )            : Effect.succeed(ready),        ensureAgentReady: () => Effect.succeed(ready),        current: Effect.succeed(ready),        stopAgent: Effect.void,        stop: Effect.void,      };    };    const http = HttpClient.make((request) =>      Effect.succeed(        HttpClientResponse.fromWeb(          request,          Response.json({            simulators: [],            emulators: [              {                id: "emulator-5554",                name: "Pixel",                version: "36",                platform: "android",                booted: true,                physical: false,              },            ],          }),        ),      ),    );    const hosts = new Map(["a", "b", "offline"].map((id) => [id, host(id, id === "offline")]));    const writeStarted = yield* Deferred.make<void>();    const finishWrite = yield* Deferred.make<void>();    const order: string[] = [];    const service = yield* makeWithHosts(hosts, undefined, () =>      Effect.gen(function* () {        order.push("write started");        yield* Deferred.succeed(writeStarted, undefined);        yield* Deferred.await(finishWrite);        order.push("write finished");        return "/host-config.json";      }),    ).pipe(Effect.provideService(HttpClient.HttpClient, http));    expect(yield* service.agentReadinessIfSupported("b")).not.toBeNull();    const listed = yield* service.list;    expect(listed.devices.map((device) => device.hostId).sort()).toEqual(["a", "b"]);    expect(listed.hostStatuses.offline?.status).toBe("failed");    const threadId = ThreadId.make("thread");    for (const hostId of ["a", "b"])      yield* service.open({ threadId, hostId, deviceId: "emulator-5554", platform: "android" });    yield* service.close({ threadId, hostId: "a", deviceId: "emulator-5554" });    const state = yield* service.state;    expect(state.devices).toHaveLength(2);    expect(state.sessions.map((session) => session.hostId)).toEqual(["b"]);    expect(state.hostStatuses.a?.status).toBe("ready");    expect(state.hostStatuses.offline?.status).toBe("failed");    const targeting = yield* service      .agentTarget({ threadId, hostId: "b", deviceId: "emulator-5554" })      .pipe(Effect.forkChild);    yield* Deferred.await(writeStarted);    const replacing = yield* service      .withLifecycleLock(        Effect.gen(function* () {          order.push("replace");          hosts.set("b", host("b"));          yield* service.refreshHosts;        }),      )      .pipe(Effect.forkChild);    yield* Deferred.succeed(finishWrite, undefined);    yield* Fiber.join(targeting);    yield* Fiber.join(replacing);    expect(order).toEqual(["write started", "write finished", "replace"]);    const replaced = yield* service.state;    expect(replaced.sessions).toEqual([]);    expect(replaced.devices.map((device) => device.hostId)).toEqual(["a"]);    expect(replaced.hostStatuses.b).toBeUndefined();    yield* service.open({ threadId, hostId: "b", deviceId: "emulator-5554", platform: "android" });    hosts.delete("b");    yield* service.refreshHosts;    yield* service.setHostStatus("b", { status: "ready" });    expect((yield* service.state).hostStatuses.b).toBeUndefined();    expect((yield* service.state).sessions).toEqual([]);    yield* service.agentReadinessIfSupported("a");    expect((yield* service.state).hostStatuses.a?.status).toBe("ready");    yield* service.configure({ enabled: false });    expect((yield* service.state).hostStatuses).toEqual({});  }).pipe(    Effect.provide(      ServerSettingsService.layerTest({ enableDeviceSupport: true, enableAgentDeviceAccess: true }),    ),  ),);