300 lines · 10.8 KB · TypeScript
RawDownload
import { assert, describe, it } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import { NetService } from "@t3tools/shared/Net";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Fiber from "effect/Fiber";import * as Layer from "effect/Layer";import * as Result from "effect/Result";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { TestClock } from "effect/testing";import { HttpClient, HttpClientResponse } from "effect/unstable/http";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { SshPasswordPrompt } from "./auth.ts";import {  buildRemoteLaunchScript,  buildRemotePairingScript,  buildRemoteStopScript,  buildRemoteT3RunnerScript,  describeReadinessCause,  issueRemotePairingToken,  REMOTE_PICK_PORT_SCRIPT,  SshEnvironmentManager,  waitForHttpReady,} from "./tunnel.ts"; const makeSuccessfulProcess = (stdout: string) => {  const stdoutStream = Stream.make(new TextEncoder().encode(stdout));  return ChildProcessSpawner.makeHandle({    pid: ChildProcessSpawner.ProcessId(123),    stdout: stdoutStream,    stderr: Stream.empty,    all: stdoutStream,    exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),    isRunning: Effect.succeed(false),    kill: () => Effect.void,    stdin: Sink.drain,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,    unref: Effect.succeed(Effect.void),  });}; const makeRunningProcess = (onKill: () => void) => {  let finish: ((exitCode: ChildProcessSpawner.ExitCode) => void) | null = null;  return ChildProcessSpawner.makeHandle({    pid: ChildProcessSpawner.ProcessId(123),    stdout: Stream.empty,    stderr: Stream.empty,    all: Stream.empty,    exitCode: Effect.callback<ChildProcessSpawner.ExitCode>((resume) => {      finish = (exitCode) => resume(Effect.succeed(exitCode));      return Effect.sync(() => {        finish = null;      });    }),    isRunning: Effect.succeed(true),    kill: () =>      Effect.sync(() => {        onKill();        finish?.(ChildProcessSpawner.ExitCode(143));      }),    stdin: Sink.drain,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,    unref: Effect.succeed(Effect.void),  });}; const testHttpClient = HttpClient.make((request) =>  Effect.succeed(HttpClientResponse.fromWeb(request, new Response("", { status: 200 }))),); const hangingHttpClient = HttpClient.make(() => Effect.never); const testNetService = NetService.of({  canListenOnHost: () => Effect.succeed(true),  isPortAvailableOnLoopback: () => Effect.succeed(true),  reserveLoopbackPort: () => Effect.succeed(41_773),  findAvailablePort: (preferred) => Effect.succeed(preferred),}); function commandArgs(command: ChildProcess.Command): ReadonlyArray<string> {  return command._tag === "StandardCommand" ? command.args : [];} describe("ssh tunnel scripts", () => {  it("builds the remote t3 runner with npx and npm fallbacks", () => {    const script = buildRemoteT3RunnerScript();     assert.include(script, "T3_NODE_SCRIPT_PATH=''");    assert.include(script, 'exec t3 "$@"');    assert.include(script, "exec npx --yes 't3@latest' \"$@\"");    assert.include(script, "exec npm exec --yes 't3@latest' -- \"$@\"");    assert.include(script, "could not install 't3@latest'");  });   it("shell-quotes package specs in the remote t3 runner", () => {    const script = buildRemoteT3RunnerScript({      packageSpec: "t3@nightly; touch /tmp/t3-owned",    });     assert.include(script, "exec npx --yes 't3@nightly; touch /tmp/t3-owned' \"$@\"");    assert.include(script, "exec npm exec --yes 't3@nightly; touch /tmp/t3-owned' -- \"$@\"");    assert.notInclude(script, "exec npx --yes t3@nightly; touch /tmp/t3-owned");  });   it("builds the remote t3 runner with a node script override", () => {    const script = buildRemoteT3RunnerScript({      nodeScriptPath: "/Users/julius/Development/Work/codething-mvp/apps/server/dist/bin.mjs",    });     assert.include(      script,      "T3_NODE_SCRIPT_PATH='/Users/julius/Development/Work/codething-mvp/apps/server/dist/bin.mjs'",    );    assert.include(script, 'exec node "$T3_NODE_SCRIPT_PATH" "$@"');  });   it("uses the remote t3 runner for launch and pairing scripts", () => {    const target = {      alias: "devbox",      hostname: "devbox.example.com",      username: "julius",      port: 2222,    } as const;     assert.include(      buildRemoteLaunchScript(),      '[ -n "$REMOTE_PID" ] && [ -n "$REMOTE_PORT" ] && kill -0 "$REMOTE_PID" 2>/dev/null',    );    assert.include(buildRemoteLaunchScript(), "RUNNER_CHANGED=1");    assert.include(buildRemoteLaunchScript(), 'kill "$REMOTE_PID" 2>/dev/null || true');    assert.include(buildRemoteLaunchScript(), "wait_ready");    assert.include(buildRemoteLaunchScript(), '"$RUNNER_FILE" serve --host 127.0.0.1');    assert.include(buildRemoteLaunchScript(), '--base-dir "$DEFAULT_SERVER_HOME"');    assert.notInclude(buildRemoteLaunchScript(), "server-home");    assert.include(buildRemoteLaunchScript(), "Remote T3 server did not become ready");    assert.include(buildRemoteLaunchScript({ packageSpec: "t3@nightly" }), "t3@nightly");    assert.include(      buildRemotePairingScript(target),      '"$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json',    );    assert.include(buildRemotePairingScript(target), 'PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME"');    assert.notInclude(buildRemotePairingScript(target), "server-home");    assert.include(buildRemotePairingScript(target, { packageSpec: "t3@nightly" }), "t3@nightly");    assert.include(      buildRemoteStopScript(target),      'if [ "$REMOTE_MANAGED" != "external" ] && [ -n "$REMOTE_PID" ]',    );    assert.include(buildRemoteStopScript(target), 'kill "$REMOTE_PID" 2>/dev/null || true');    assert.include(buildRemoteStopScript(target), 'rm -f "$PID_FILE" "$PORT_FILE" "$MANAGED_FILE"');    assert.include(      buildRemoteLaunchScript(),      'DEFAULT_RUNTIME_FILE="$DEFAULT_SERVER_HOME/userdata/server-runtime.json"',    );    assert.include(buildRemoteLaunchScript(), "resolve_default_runtime_port()");    assert.include(buildRemoteLaunchScript(), "printf 'external\\n' >\"$MANAGED_FILE\"");    assert.isBelow(      buildRemoteLaunchScript().indexOf('if [ "$REMOTE_MANAGED" = "managed" ]'),      buildRemoteLaunchScript().indexOf("printf 'external\\n' >\"$MANAGED_FILE\""),    );    assert.isBelow(      buildRemoteLaunchScript().indexOf('DEFAULT_REMOTE_PORT="$(resolve_default_runtime_port'),      buildRemoteLaunchScript().indexOf('elif [ -n "$REMOTE_PID" ]'),    );  });   it("allows the remote port picker to run without a state file path", () => {    assert.include(REMOTE_PICK_PORT_SCRIPT, 'const filePath = process.argv[2] ?? "";');  });   it.effect("bounds each HTTP readiness probe so retries cannot hang on one request", () =>    Effect.gen(function* () {      const fiber = yield* Effect.forkChild(        Effect.result(          waitForHttpReady({            baseUrl: "http://127.0.0.1:41773/",            timeoutMs: 1_000,            intervalMs: 100,            probeTimeoutMs: 250,          }),        ),      );      yield* Effect.yieldNow;      yield* TestClock.adjust(Duration.millis(1_000));       const result = yield* Fiber.join(fiber);       assert.isTrue(Result.isFailure(result));      if (Result.isFailure(result)) {        assert.include(result.failure.message, "Timed out waiting 1000ms");      }    }).pipe(      Effect.provide(        Layer.merge(TestClock.layer(), Layer.succeed(HttpClient.HttpClient, hangingHttpClient)),      ),    ),  );   it("preserves primitive readiness reason values in diagnostic output", () => {    assert.deepEqual(      describeReadinessCause({        _tag: "HttpClientError",        message: "Backend readiness probe failed.",        reason: "authentication failed",        cause: "upstream closed",      }),      {        _tag: "HttpClientError",        message: "Backend readiness probe failed.",        reason: "authentication failed",        cause: "upstream closed",      },    );  });   it.effect("accepts pretty-printed pairing JSON from the remote CLI", () => {    const target = {      alias: "devbox",      hostname: "devbox.example.com",      username: "julius",      port: 2222,    } as const;    const spawner = ChildProcessSpawner.make(() =>      Effect.succeed(        makeSuccessfulProcess(`{  "id": "88941235-6ed5-4184-a2ff-5339e2075958",  "credential": "LCL4R2TPHDKQ",  "role": "client",  "expiresAt": "2026-04-29T01:01:20.994Z"} `),      ),    );    const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);    const processLayer = Layer.merge(NodeServices.layer, spawnerLayer);    return Effect.gen(function* () {      const result = yield* issueRemotePairingToken(target);      assert.equal(result.credential, "LCL4R2TPHDKQ");    }).pipe(Effect.provide(processLayer));  });   it.effect("closes the tunnel scope and starts fresh after disconnect", () => {    const spawnedCommands: Array<ReadonlyArray<string>> = [];    let tunnelKillCount = 0;    let stopCommandCount = 0;    const spawner = ChildProcessSpawner.make((command) =>      Effect.sync(() => {        const args = commandArgs(command);        spawnedCommands.push(args);        if (args.includes("-N")) {          return makeRunningProcess(() => {            tunnelKillCount += 1;          });        }        if (args.includes("sh") && args.includes("--")) {          return makeSuccessfulProcess('{"remotePort":3773}\n');        }        if (args.includes("sh")) {          stopCommandCount += 1;          return makeSuccessfulProcess('{"stopped":true}\n');        }        return makeSuccessfulProcess("\n");      }),    );    const layer = Layer.mergeAll(      NodeServices.layer,      Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner),      Layer.succeed(HttpClient.HttpClient, testHttpClient),      Layer.succeed(NetService, testNetService),      SshPasswordPrompt.disabledLayer,      SshEnvironmentManager.layer(),    );    const target = {      alias: "devbox",      hostname: "devbox.example.com",      username: "julius",      port: 2222,    } as const;     return Effect.gen(function* () {      const manager = yield* SshEnvironmentManager;       const first = yield* manager.ensureEnvironment(target);      assert.equal(first.httpBaseUrl, "http://127.0.0.1:41773/");       yield* manager.disconnectEnvironment(target);      assert.equal(tunnelKillCount, 1);      assert.equal(stopCommandCount, 1);       yield* manager.ensureEnvironment(target);       assert.equal(spawnedCommands.filter((args) => args.includes("-N")).length, 2);      assert.equal(tunnelKillCount, 1);    }).pipe(Effect.provide(layer), Effect.scoped);  });});