import { assert, it } from "@effect/vitest"; import { DateTime, Effect, Layer, Option } from "effect"; import { ChildProcessSpawner } from "effect/unstable/process"; import * as VcsProcess from "../vcs/VcsProcess.ts"; import * as GitHubCli from "./GitHubCli.ts"; import * as GitHubSourceControlProvider from "./GitHubSourceControlProvider.ts"; const processResult = (stdout: string): VcsProcess.VcsProcessOutput => ({ exitCode: ChildProcessSpawner.ExitCode(0), stdout, stderr: "", stdoutTruncated: false, stderrTruncated: false, }); function makeProvider(github: Partial) { return GitHubSourceControlProvider.make().pipe( Effect.provide(Layer.mock(GitHubCli.GitHubCli)(github)), ); } it.effect("maps GitHub PR summaries into provider-neutral change requests", () => Effect.gen(function* () { const provider = yield* makeProvider({ getPullRequest: () => Effect.succeed({ number: 42, title: "Add GitHub provider", url: "https://github.com/pingdotgg/t3code/pull/42", baseRefName: "main", headRefName: "feature/source-control", state: "open", isCrossRepository: true, headRepositoryNameWithOwner: "fork/t3code", headRepositoryOwnerLogin: "fork", }), }); const changeRequest = yield* provider.getChangeRequest({ cwd: "/repo", reference: "42", }); assert.deepStrictEqual(changeRequest, { provider: "github", number: 42, title: "Add GitHub provider", url: "https://github.com/pingdotgg/t3code/pull/42", baseRefName: "main", headRefName: "feature/source-control", state: "open", updatedAt: Option.none(), isCrossRepository: true, headRepositoryNameWithOwner: "fork/t3code", headRepositoryOwnerLogin: "fork", }); }), ); it.effect("uses gh json listing for non-open change request state queries", () => Effect.gen(function* () { let executeArgs: ReadonlyArray = []; const provider = yield* makeProvider({ execute: (input) => { executeArgs = input.args; return Effect.succeed( processResult( JSON.stringify([ { number: 7, title: "Merged work", url: "https://github.com/pingdotgg/t3code/pull/7", baseRefName: "main", headRefName: "feature/merged", state: "merged", updatedAt: "2026-01-02T00:00:00.000Z", }, ]), ), ); }, }); const changeRequests = yield* provider.listChangeRequests({ cwd: "/repo", headSelector: "feature/merged", state: "all", limit: 10, }); assert.deepStrictEqual(executeArgs, [ "pr", "list", "--head", "feature/merged", "--state", "all", "--limit", "10", "--json", "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner", ]); assert.strictEqual(changeRequests[0]?.provider, "github"); assert.strictEqual(changeRequests[0]?.state, "merged"); assert.deepStrictEqual( changeRequests[0]?.updatedAt, Option.some(DateTime.makeUnsafe("2026-01-02T00:00:00.000Z")), ); }), ); it.effect("treats empty non-open change request listing output as no results", () => Effect.gen(function* () { const provider = yield* makeProvider({ execute: () => Effect.succeed(processResult("")), }); const changeRequests = yield* provider.listChangeRequests({ cwd: "/repo", headSelector: "feature/empty", state: "all", limit: 10, }); assert.deepStrictEqual(changeRequests, []); }), ); it.effect("creates GitHub PRs through provider-neutral input names", () => Effect.gen(function* () { let createInput: Parameters[0] | null = null; const provider = yield* makeProvider({ createPullRequest: (input) => { createInput = input; return Effect.void; }, }); yield* provider.createChangeRequest({ cwd: "/repo", baseRefName: "main", headSelector: "owner:feature/provider", title: "Provider PR", bodyFile: "/tmp/body.md", }); assert.deepStrictEqual(createInput, { cwd: "/repo", baseBranch: "main", headSelector: "owner:feature/provider", title: "Provider PR", bodyFile: "/tmp/body.md", }); }), );