import { assert, it } from "@effect/vitest"; import { Effect } from "effect"; import { afterEach, expect, vi } from "vitest"; vi.mock("../../processRunner", () => ({ runProcess: vi.fn(), })); import { runProcess } from "../../processRunner.ts"; import { GitHubCli } from "../Services/GitHubCli.ts"; import { GitHubCliLive } from "./GitHubCli.ts"; const mockedRunProcess = vi.mocked(runProcess); const layer = it.layer(GitHubCliLive); afterEach(() => { mockedRunProcess.mockReset(); }); layer("GitHubCliLive", (it) => { it.effect("parses pull request view output", () => Effect.gen(function* () { mockedRunProcess.mockResolvedValueOnce({ stdout: 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", }, }), stderr: "", code: 0, signal: null, timedOut: false, }); const result = yield* Effect.gen(function* () { const gh = yield* GitHubCli; return 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(mockedRunProcess).toHaveBeenCalledWith( "gh", [ "pr", "view", "#42", "--json", "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner", ], expect.objectContaining({ cwd: "/repo" }), ); }), ); it.effect("trims pull request fields decoded from gh json", () => Effect.gen(function* () { mockedRunProcess.mockResolvedValueOnce({ stdout: 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 ", }, }), stderr: "", code: 0, signal: null, timedOut: false, }); const result = yield* Effect.gen(function* () { const gh = yield* GitHubCli; return 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", }); }), ); it.effect("skips invalid entries when parsing pr lists", () => Effect.gen(function* () { mockedRunProcess.mockResolvedValueOnce({ stdout: 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: " ", }, }, ]), stderr: "", code: 0, signal: null, timedOut: false, }); const result = yield* Effect.gen(function* () { const gh = yield* GitHubCli; return 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", }, ]); }), ); it.effect("reads repository clone URLs", () => Effect.gen(function* () { mockedRunProcess.mockResolvedValueOnce({ stdout: JSON.stringify({ nameWithOwner: "octocat/codething-mvp", url: "https://github.com/octocat/codething-mvp", sshUrl: "git@github.com:octocat/codething-mvp.git", }), stderr: "", code: 0, signal: null, timedOut: false, }); const result = yield* Effect.gen(function* () { const gh = yield* GitHubCli; return 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", }); }), ); it.effect("surfaces a friendly error when the pull request is not found", () => Effect.gen(function* () { mockedRunProcess.mockRejectedValueOnce( new Error( "GraphQL: Could not resolve to a PullRequest with the number of 4888. (repository.pullRequest)", ), ); const error = yield* Effect.gen(function* () { const gh = yield* GitHubCli; return yield* gh.getPullRequest({ cwd: "/repo", reference: "4888", }); }).pipe(Effect.flip); assert.equal(error.message.includes("Pull request not found"), true); }), ); });