296 lines · 9.0 KB · TypeScript
RawDownload
import { assert, it, afterEach, describe, expect, vi } from "@effect/vitest";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import { ChildProcessSpawner } from "effect/unstable/process";import { VcsProcessExitError } from "@t3tools/contracts"; import * as VcsProcess from "../vcs/VcsProcess.ts";import * as GitHubCli from "./GitHubCli.ts"; const processOutput = (stdout: string): VcsProcess.VcsProcessOutput => ({  exitCode: ChildProcessSpawner.ExitCode(0),  stdout,  stderr: "",  stdoutTruncated: false,  stderrTruncated: false,}); const mockRun = vi.fn<VcsProcess.VcsProcessShape["run"]>(); const layer = GitHubCli.layer.pipe(  Layer.provide(    Layer.mock(VcsProcess.VcsProcess)({      run: mockRun,    }),  ),); afterEach(() => {  mockRun.mockReset();}); describe("GitHubCli.layer", () => {  it.effect("parses pull request view output", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.succeed(          processOutput(            // @effect-diagnostics-next-line preferSchemaOverJson:off            JSON.stringify({              number: 42,              title: "Add PR thread creation",              url: "https://github.com/pingdotgg/codething-mvp/pull/42",              baseRefName: "main",              headRefName: "feature/pr-threads",              state: "OPEN",              mergedAt: null,              isCrossRepository: true,              headRepository: {                nameWithOwner: "octocat/codething-mvp",              },              headRepositoryOwner: {                login: "octocat",              },            }),          ),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.getPullRequest({        cwd: "/repo",        reference: "#42",      });       assert.deepStrictEqual(result, {        number: 42,        title: "Add PR thread creation",        url: "https://github.com/pingdotgg/codething-mvp/pull/42",        baseRefName: "main",        headRefName: "feature/pr-threads",        state: "open",        isCrossRepository: true,        headRepositoryNameWithOwner: "octocat/codething-mvp",        headRepositoryOwnerLogin: "octocat",      });      expect(mockRun).toHaveBeenCalledWith({        operation: "GitHubCli.execute",        command: "gh",        args: [          "pr",          "view",          "#42",          "--json",          "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner",        ],        cwd: "/repo",        timeoutMs: 30_000,      });    }).pipe(Effect.provide(layer)),  );   it.effect("trims pull request fields decoded from gh json", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.succeed(          processOutput(            // @effect-diagnostics-next-line preferSchemaOverJson:off            JSON.stringify({              number: 42,              title: "  Add PR thread creation  \n",              url: " https://github.com/pingdotgg/codething-mvp/pull/42 ",              baseRefName: " main ",              headRefName: "\tfeature/pr-threads\t",              state: "OPEN",              mergedAt: null,              isCrossRepository: true,              headRepository: {                nameWithOwner: " octocat/codething-mvp ",              },              headRepositoryOwner: {                login: " octocat ",              },            }),          ),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.getPullRequest({        cwd: "/repo",        reference: "#42",      });       assert.deepStrictEqual(result, {        number: 42,        title: "Add PR thread creation",        url: "https://github.com/pingdotgg/codething-mvp/pull/42",        baseRefName: "main",        headRefName: "feature/pr-threads",        state: "open",        isCrossRepository: true,        headRepositoryNameWithOwner: "octocat/codething-mvp",        headRepositoryOwnerLogin: "octocat",      });    }).pipe(Effect.provide(layer)),  );   it.effect("skips invalid entries when parsing pr lists", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.succeed(          processOutput(            // @effect-diagnostics-next-line preferSchemaOverJson:off            JSON.stringify([              {                number: 0,                title: "invalid",                url: "https://github.com/pingdotgg/codething-mvp/pull/0",                baseRefName: "main",                headRefName: "feature/invalid",              },              {                number: 43,                title: "  Valid PR  ",                url: " https://github.com/pingdotgg/codething-mvp/pull/43 ",                baseRefName: " main ",                headRefName: " feature/pr-list ",                headRepository: {                  nameWithOwner: "   ",                },                headRepositoryOwner: {                  login: "   ",                },              },            ]),          ),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.listOpenPullRequests({        cwd: "/repo",        headSelector: "feature/pr-list",      });       assert.deepStrictEqual(result, [        {          number: 43,          title: "Valid PR",          url: "https://github.com/pingdotgg/codething-mvp/pull/43",          baseRefName: "main",          headRefName: "feature/pr-list",          state: "open",        },      ]);    }).pipe(Effect.provide(layer)),  );   it.effect("reads repository clone URLs", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.succeed(          processOutput(            // @effect-diagnostics-next-line preferSchemaOverJson:off            JSON.stringify({              nameWithOwner: "octocat/codething-mvp",              url: "https://github.com/octocat/codething-mvp",              sshUrl: "git@github.com:octocat/codething-mvp.git",            }),          ),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.getRepositoryCloneUrls({        cwd: "/repo",        repository: "octocat/codething-mvp",      });       assert.deepStrictEqual(result, {        nameWithOwner: "octocat/codething-mvp",        url: "https://github.com/octocat/codething-mvp",        sshUrl: "git@github.com:octocat/codething-mvp.git",      });    }).pipe(Effect.provide(layer)),  );   it.effect("creates repositories and parses clone URLs from create output", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.succeed(          processOutput(            "✓ Created repository octocat/codething-mvp on github.com\nhttps://github.com/octocat/codething-mvp\n",          ),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.createRepository({        cwd: "/repo",        repository: "octocat/codething-mvp",        visibility: "private",      });       assert.deepStrictEqual(result, {        nameWithOwner: "octocat/codething-mvp",        url: "https://github.com/octocat/codething-mvp",        sshUrl: "git@github.com:octocat/codething-mvp.git",      });      expect(mockRun).toHaveBeenCalledTimes(1);      expect(mockRun).toHaveBeenNthCalledWith(1, {        operation: "GitHubCli.execute",        command: "gh",        args: ["repo", "create", "octocat/codething-mvp", "--private"],        cwd: "/repo",        timeoutMs: 30_000,      });    }).pipe(Effect.provide(layer)),  );   it.effect("falls back to constructed URLs when create output omits a URL", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(Effect.succeed(processOutput("")));       const gh = yield* GitHubCli.GitHubCli;      const result = yield* gh.createRepository({        cwd: "/repo",        repository: "octocat/codething-mvp",        visibility: "private",      });       assert.deepStrictEqual(result, {        nameWithOwner: "octocat/codething-mvp",        url: "https://github.com/octocat/codething-mvp",        sshUrl: "git@github.com:octocat/codething-mvp.git",      });    }).pipe(Effect.provide(layer)),  );   it.effect("surfaces a friendly error when the pull request is not found", () =>    Effect.gen(function* () {      mockRun.mockReturnValueOnce(        Effect.fail(          new VcsProcessExitError({            operation: "GitHubCli.execute",            command: "gh pr view",            cwd: "/repo",            exitCode: 1,            detail:              "GraphQL: Could not resolve to a PullRequest with the number of 4888. (repository.pullRequest)",          }),        ),      );       const gh = yield* GitHubCli.GitHubCli;      const error = yield* gh        .getPullRequest({          cwd: "/repo",          reference: "4888",        })        .pipe(Effect.flip);       assert.equal(error.message.includes("Pull request not found"), true);    }).pipe(Effect.provide(layer)),  );});