237 lines · 7.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 { ChildProcessSpawner } from "effect/unstable/process"; import {  baseSshArgs,  getLastNonEmptyOutputLine,  parseSshResolveOutput,  resolveRemoteT3CliPackageSpec,  runSshCommand,} from "./command.ts";import { SshCommandError } 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("includes stdout in non-zero command failures when stderr is empty", () => {    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, SshCommandError);        assert.equal(result.failure.message, "Pairing token creation failed");        assert.equal(result.failure.stdout, "Pairing token creation failed\n");        assert.equal(result.failure.stderr, "");      }    }).pipe(Effect.provide(processLayer));  });   it.effect("redacts credentials from stdout in non-zero command failures", () => {    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, SshCommandError);        assert.equal(result.failure.message, '{"credential":"[redacted]"}');        assert.equal(result.failure.stdout, '{"credential":"[redacted]"}\n');      }    }).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: "devbox",              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.include(result.failure.message, "SSH command timed out after 1ms.");      }    }).pipe(Effect.provide(processLayer));  });});