257 lines · 8.2 KB · TypeScript
RawDownload
import { assert, it } from "@effect/vitest";import * as NodeServices from "@effect/platform-node/NodeServices";import { Effect, Layer, Option } from "effect";import { ChildProcessSpawner } from "effect/unstable/process";import { VcsProcessSpawnError } from "@t3tools/contracts"; import { ServerConfig } from "../config.ts";import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";import * as VcsProcess from "../vcs/VcsProcess.ts";import * as AzureDevOpsCli from "./AzureDevOpsCli.ts";import * as BitbucketApi from "./BitbucketApi.ts";import * as GitHubCli from "./GitHubCli.ts";import * as GitLabCli from "./GitLabCli.ts";import * as SourceControlDiscovery from "./SourceControlDiscovery.ts";import * as SourceControlProviderRegistry from "./SourceControlProviderRegistry.ts"; const sourceControlProviderRegistryTestLayer = (input: {  readonly bitbucket: Partial<BitbucketApi.BitbucketApiShape>;  readonly process: Partial<VcsProcess.VcsProcessShape>;}) =>  SourceControlProviderRegistry.layer.pipe(    Layer.provide(      Layer.mergeAll(        ServerConfig.layerTest(process.cwd(), { prefix: "t3-source-control-registry-test-" }).pipe(          Layer.provide(NodeServices.layer),        ),        Layer.mock(AzureDevOpsCli.AzureDevOpsCli)({}),        Layer.mock(BitbucketApi.BitbucketApi)(input.bitbucket),        Layer.mock(GitHubCli.GitHubCli)({}),        Layer.mock(GitLabCli.GitLabCli)({}),        Layer.mock(VcsDriverRegistry.VcsDriverRegistry)({}),        Layer.mock(VcsProcess.VcsProcess)(input.process),      ),    ),  ); const processOutput = (  stdout: string,  options?: {    readonly stderr?: string;    readonly exitCode?: ChildProcessSpawner.ExitCode;  },): VcsProcess.VcsProcessOutput => ({  exitCode: options?.exitCode ?? ChildProcessSpawner.ExitCode(0),  stdout,  stderr: options?.stderr ?? "",  stdoutTruncated: false,  stderrTruncated: false,}); it.effect("reports implemented tools separately from locally available executables", () => {  const processMock = {    run: (input: VcsProcess.VcsProcessInput) => {      if (input.command === "git") {        return Effect.succeed(processOutput("git version 2.51.0\n"));      }      if (input.command === "gh" && input.args[0] === "--version") {        return Effect.succeed(processOutput("gh version 2.83.0\n"));      }      if (input.command === "gh" && input.args.join(" ") === "auth status") {        return Effect.succeed(          processOutput(`github.comLogged in to github.com account juliusmarminge (keyring)- Active account: true- Git operations protocol: ssh- Token: gho_************************************- Token scopes: 'admin:public_key', 'gist', 'read:org', 'repo'`),        );      }      return Effect.fail(        new VcsProcessSpawnError({          operation: input.operation,          command: input.command,          cwd: input.cwd,          cause: new Error(`${input.command} not found`),        }),      );    },  } satisfies Partial<VcsProcess.VcsProcessShape>;  const testLayer = SourceControlDiscovery.layer.pipe(    Layer.provide(      ServerConfig.layerTest(process.cwd(), { prefix: "t3-source-control-discovery-" }),    ),    Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)),    Layer.provide(      sourceControlProviderRegistryTestLayer({        process: processMock,        bitbucket: {          probeAuth: Effect.succeed({            status: "unauthenticated",            account: Option.none(),            host: Option.some("bitbucket.org"),            detail: Option.some(              "Set T3CODE_BITBUCKET_EMAIL and T3CODE_BITBUCKET_API_TOKEN, or T3CODE_BITBUCKET_ACCESS_TOKEN.",            ),          }),        },      }),    ),    Layer.provideMerge(NodeServices.layer),  );   return Effect.gen(function* () {    const discovery = yield* SourceControlDiscovery.SourceControlDiscovery;    const result = yield* discovery.discover;     assert.deepStrictEqual(      result.versionControlSystems.map((item) => ({        kind: item.kind,        implemented: item.implemented,        status: item.status,      })),      [        { kind: "git", implemented: true, status: "available" },        { kind: "jj", implemented: false, status: "missing" },      ],    );    assert.deepStrictEqual(      result.sourceControlProviders.map((item) => ({        kind: item.kind,        status: item.status,        auth: item.auth.status,        account: item.auth.account,      })),      [        {          kind: "github",          status: "available",          auth: "authenticated",          account: Option.some("juliusmarminge"),        },        {          kind: "gitlab",          status: "missing",          auth: "unknown",          account: Option.none(),        },        {          kind: "azure-devops",          status: "missing",          auth: "unknown",          account: Option.none(),        },        {          kind: "bitbucket",          status: "available",          auth: "unauthenticated",          account: Option.none(),        },      ],    );    const bitbucket = result.sourceControlProviders.find((item) => item.kind === "bitbucket");    assert.ok(bitbucket);    assert.strictEqual(bitbucket.executable, undefined);  }).pipe(Effect.provide(testLayer));}); it.effect("probes provider authentication without exposing token details", () => {  const processMock = {    run: (input: VcsProcess.VcsProcessInput) => {      if (input.args[0] === "--version") {        return Effect.succeed(processOutput(`${input.command} version test\n`));      }      if (input.command === "gh" && input.args.join(" ") === "auth status") {        return Effect.succeed(          processOutput(`github.comLogged in to github.com account octocat (keyring)- Token: gho_************************************- Token scopes: 'repo'`),        );      }      if (input.command === "glab" && input.args.join(" ") === "auth status") {        return Effect.succeed(          processOutput(`gitlab.comLogged in to gitlab.com as gitlab-user`),        );      }      if (        input.command === "az" &&        input.args.join(" ") === "account show --query user.name -o tsv"      ) {        return Effect.succeed(processOutput("azure-user@example.com\n"));      }      return Effect.fail(        new VcsProcessSpawnError({          operation: input.operation,          command: input.command,          cwd: input.cwd,          cause: new Error(`${input.command} not found`),        }),      );    },  } satisfies Partial<VcsProcess.VcsProcessShape>;  const testLayer = SourceControlDiscovery.layer.pipe(    Layer.provide(      ServerConfig.layerTest(process.cwd(), { prefix: "t3-source-control-auth-discovery-" }),    ),    Layer.provide(Layer.mock(VcsProcess.VcsProcess)(processMock)),    Layer.provide(      sourceControlProviderRegistryTestLayer({        process: processMock,        bitbucket: {          probeAuth: Effect.succeed({            status: "authenticated",            account: Option.some("bitbucket-user"),            host: Option.some("bitbucket.org"),            detail: Option.none(),          }),        },      }),    ),    Layer.provideMerge(NodeServices.layer),  );   return Effect.gen(function* () {    const discovery = yield* SourceControlDiscovery.SourceControlDiscovery;    const result = yield* discovery.discover;     assert.deepStrictEqual(      result.sourceControlProviders.map((item) => ({        kind: item.kind,        auth: item.auth.status,        account: item.auth.account,        detail: item.auth.detail,      })),      [        {          kind: "github",          auth: "authenticated",          account: Option.some("octocat"),          detail: Option.none(),        },        {          kind: "gitlab",          auth: "authenticated",          account: Option.some("gitlab-user"),          detail: Option.none(),        },        {          kind: "azure-devops",          auth: "authenticated",          account: Option.some("azure-user@example.com"),          detail: Option.none(),        },        {          kind: "bitbucket",          auth: "authenticated",          account: Option.some("bitbucket-user"),          detail: Option.none(),        },      ],    );  }).pipe(Effect.provide(testLayer));});