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); }), );});
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);
}),
);
});