Julius MarmingeNormalize SSH timeout targets2b84cc04 months agoHistory
262 lines · 8.4 KB · TypeScript
RawDownload
import { assert, describe, it } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";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 * as TestClock from "effect/testing/TestClock";import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import {  baseSshArgs,  getLastNonEmptyOutputLine,  parseSshResolveOutput,  resolveRemoteT3CliPackageSpec,  runSshCommand,} from "./command.ts";import { SshCommandExitError, SshCommandSpawnError } from "./errors.ts"; const encoder = new TextEncoder(); const makeFailedProcess = (input: { readonly stdout: string; readonly stderr?: string }) => {  const stdoutStream = Stream.make(encoder.encode(input.stdout));  const stderrStream = input.stderr ? Stream.make(encoder.encode(input.stderr)) : Stream.empty;  return ChildProcessSpawner.makeHandle({    pid: ChildProcessSpawner.ProcessId(123),    stdout: stdoutStream,    stderr: stderrStream,    all: Stream.empty,    exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(1)),    isRunning: Effect.succeed(false),    kill: () => Effect.void,    stdin: Sink.drain,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,    unref: Effect.succeed(Effect.void),  });}; const makeNeverFinishingProcess = () => {  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(() => {        finish?.(ChildProcessSpawner.ExitCode(143));      }),    stdin: Sink.drain,    getInputFd: () => Sink.drain,    getOutputFd: () => Stream.empty,    unref: Effect.succeed(Effect.void),  });}; describe("ssh command", () => {  it.effect("parses resolved ssh config output into a target", () =>    Effect.sync(() => {      assert.deepEqual(        parseSshResolveOutput(          "devbox",          ["hostname devbox.example.com", "user julius", "port 2222", ""].join("\n"),        ),        {          alias: "devbox",          hostname: "devbox.example.com",          username: "julius",          port: 2222,        },      );    }),  );   it.effect("builds interactive ssh args without forcing batch mode", () =>    Effect.sync(() => {      assert.deepEqual(        baseSshArgs(          {            alias: "devbox",            hostname: "devbox.example.com",            username: "julius",            port: 2222,          },          { batchMode: "no" },        ),        ["-o", "BatchMode=no", "-o", "ConnectTimeout=10", "-p", "2222"],      );    }),  );   it.effect("resolves the remote t3 package spec from the desktop release channel", () =>    Effect.sync(() => {      assert.equal(        resolveRemoteT3CliPackageSpec({          appVersion: "0.0.17",          updateChannel: "latest",        }),        "t3@0.0.17",      );      assert.equal(        resolveRemoteT3CliPackageSpec({          appVersion: "0.0.17-nightly.20260415.44",          updateChannel: "nightly",        }),        "t3@0.0.17-nightly.20260415.44",      );      assert.equal(        resolveRemoteT3CliPackageSpec({          appVersion: "0.0.0-dev",          updateChannel: "nightly",          isDevelopment: true,        }),        "t3@nightly",      );      assert.equal(        resolveRemoteT3CliPackageSpec({          appVersion: "0.0.0-dev",          updateChannel: "latest",          isDevelopment: true,        }),        "t3@nightly",      );    }),  );   it.effect("reads the last non-empty ssh output line", () =>    Effect.sync(() => {      assert.equal(        getLastNonEmptyOutputLine(          ["Welcome to the host", "", '{"credential":"pairing-token"}', ""].join("\n"),        ),        '{"credential":"pairing-token"}',      );    }),  );   it.effect("records bounded output diagnostics for non-zero command failures", () => {    const spawner = ChildProcessSpawner.make(() =>      Effect.succeed(makeFailedProcess({ stdout: "Pairing token creation failed\n" })),    );    const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);    const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer);     return Effect.gen(function* () {      const result = yield* Effect.result(        runSshCommand(          {            alias: "devbox",            hostname: "devbox.example.com",            username: "julius",            port: 2222,          },          { remoteCommandArgs: ["sh", "-s"] },        ),      );       assert.isTrue(Result.isFailure(result));      if (Result.isFailure(result)) {        assert.instanceOf(result.failure, SshCommandExitError);        assert.equal(result.failure.message, "SSH command failed for julius@devbox (exit 1).");        assert.equal(result.failure.command, "ssh");        assert.equal(result.failure.argumentCount, 9);        assert.equal(          result.failure.stdoutBytes,          encoder.encode("Pairing token creation failed\n").length,        );        assert.equal(result.failure.stderrBytes, 0);        assert.isFalse("stdout" in result.failure);        assert.isFalse("stderr" in result.failure);      }       const spawnCause = new Error("ssh executable was not found");      const spawnError = new SshCommandSpawnError({        command: "ssh",        argumentCount: 0,        target: "devbox",        cause: spawnCause,      });      assert.strictEqual(spawnError.cause, spawnCause);      assert.equal(spawnError.message, "Failed to spawn SSH command for devbox.");      assert.notInclude(spawnError.message, spawnCause.message);    }).pipe(Effect.provide(processLayer));  });   it.effect("does not expose credentials from non-zero command output", () => {    const spawner = ChildProcessSpawner.make(() =>      Effect.succeed(makeFailedProcess({ stdout: '{"credential":"pairing-secret"}\n' })),    );    const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);    const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer);     return Effect.gen(function* () {      const result = yield* Effect.result(        runSshCommand(          {            alias: "devbox",            hostname: "devbox.example.com",            username: "julius",            port: 2222,          },          { remoteCommandArgs: ["sh", "-s"] },        ),      );       assert.isTrue(Result.isFailure(result));      if (Result.isFailure(result)) {        assert.instanceOf(result.failure, SshCommandExitError);        assert.notInclude(result.failure.message, "pairing-secret");        assert.equal(          result.failure.stdoutBytes,          encoder.encode('{"credential":"pairing-secret"}\n').length,        );        assert.isFalse("stdout" in result.failure);      }    }).pipe(Effect.provide(processLayer));  });   it.effect("fails commands that never finish", () => {    const spawner = ChildProcessSpawner.make(() => Effect.succeed(makeNeverFinishingProcess()));    const spawnerLayer = Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner);    const processLayer = Layer.mergeAll(NodeServices.layer, spawnerLayer, TestClock.layer());     return Effect.gen(function* () {      const fiber = yield* Effect.forkChild(        Effect.result(          runSshCommand(            {              alias: "  ",              hostname: "devbox.example.com",              username: "julius",              port: 2222,            },            { timeoutMs: 1 },          ),        ),      );      yield* Effect.yieldNow;      yield* TestClock.adjust(Duration.millis(1));       const result = yield* Fiber.join(fiber);       assert.isTrue(Result.isFailure(result));      if (Result.isFailure(result)) {        assert.equal(          result.failure.message,          "SSH command timed out after 1ms for devbox.example.com.",        );      }    }).pipe(Effect.provide(processLayer));  });});