89 lines · 3.7 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:off - exercises concurrent real CLI subprocesses.import { HostProcessPlatform } from "@t3tools/shared/hostProcess";import { describe, expect, it } from "@effect/vitest";import * as NodeChildProcess from "node:child_process";import * as NodeUtil from "node:util";import * as NodeServices from "@effect/platform-node/NodeServices";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Path from "effect/Path";import { ensureAgentDeviceShim } from "./AgentDeviceShim.ts";import {  agentDeviceConfigPath,  agentDeviceSession,  writeAgentDeviceConfig,} from "./AgentDeviceTarget.ts"; const exec = NodeUtil.promisify(NodeChildProcess.execFile); describe("host-bound agent commands", () => {  it.effect("runs two hosts concurrently and only updates the reconnected host", () =>    Effect.gen(function* () {      const fs = yield* FileSystem.FileSystem;      const path = yield* Path.Path;      const temp = yield* fs.makeTempDirectoryScoped({ prefix: "t3-device-target-" });      const platform = yield* HostProcessPlatform;      const dir = path.join(        temp,        platform === "win32" ? "paths with spaces" : "quotes '\" $HOME `literal`",      );      yield* fs.makeDirectory(dir);      const entryPath = path.join(dir, "cli.mjs");      yield* fs.writeFileString(        entryPath,        `import { readFileSync } from 'node:fs';const args = process.argv.slice(2);console.log(readFileSync(args[args.indexOf('--config') + 1], 'utf8'));if (process.env.AGENT_DEVICE_DAEMON_BASE_URL) process.exit(2);`,      );      const shim = yield* ensureAgentDeviceShim({ entryPath, stateDir: dir });      const files = ["mini", "android"].map((host) => agentDeviceConfigPath(dir, host, path));      for (const [index, file] of files.entries())        yield* writeAgentDeviceConfig(file, {          baseUrl: `http://127.0.0.1:${1000 + index}`,          token: `token-${index}`,          entryPath,        });      const invoke = (file: string) =>        exec(          platform === "win32" ? process.execPath : path.join(shim, "agent-device"),          [            ...(platform === "win32" ? [path.join(shim, "agent-device-launcher.mjs")] : []),            "snapshot",            "--config",            file,            "--session",            "test-session",          ],          { env: { ...process.env, AGENT_DEVICE_DAEMON_BASE_URL: "http://wrong-host" } },        ).then((result) => JSON.parse(result.stdout));      expect(yield* Effect.promise(() => Promise.all(files.map(invoke)))).toEqual([        { daemonBaseUrl: "http://127.0.0.1:1000", daemonAuthToken: "token-0" },        { daemonBaseUrl: "http://127.0.0.1:1001", daemonAuthToken: "token-1" },      ]);      const second = yield* fs.readFileString(files[1]!);      yield* writeAgentDeviceConfig(files[0]!, {        baseUrl: "http://127.0.0.1:2000",        token: "new",        entryPath,      });      expect((yield* Effect.promise(() => invoke(files[0]!))).daemonAuthToken).toBe("new");      expect(yield* fs.readFileString(files[1]!)).toBe(second);      expect(agentDeviceSession("thread", "mini", "same-id")).not.toBe(        agentDeviceSession("thread", "android", "same-id"),      );      for (const args of [        ["snapshot"],        ["snapshot", "--config", files[0]!],        ["snapshot", "--config", "help"],        ["snapshot", "--config", files[0]!, "--session"],      ]) {        yield* Effect.promise(() =>          expect(            exec(process.execPath, [path.join(shim, "agent-device-launcher.mjs"), ...args]),          ).rejects.toThrow("Call device_open first"),        );      }    }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)),  );});