History

Normalize remote URL matching and migrate GitHubCli tests

- Treat remotes that only differ by trailing slash after `.git` as the same remote - Add a GitCore regression test for trailing-slash remote reuse - Refactor `GitHubCli` tests to `@effect/vitest` layer-based effect tests with reset cleanup

Julius Marminge committed 8 months ago

Commit 442bc951 parent f7570713 files · +113 −88Browse files

Unified · Split

3 files
  1. apps/server/src/git/Layers/GitCore.test.ts +19 −0
  2. apps/server/src/git/Layers/GitCore.ts +1 −1
  3. apps/server/src/git/Layers/GitHubCli.test.ts +93 −87
apps/server/src/git/Layers/GitCore.test.ts+19−0
@@ -1323,6 +1323,25 @@ it.layer(TestLayer)("git integration", (it) => { · 1322 unmodified lines
13231323 }),
13241324 );
13251325
1326+ it.effect("reuses an existing remote when the target URL only differs by a trailing slash after .git", () =>
1327+ Effect.gen(function* () {
1328+ const tmp = yield* makeTmpDir();
1329+ yield* initRepoWithCommit(tmp);
1330+ const core = yield* GitCore;
1331+
1332+ yield* git(tmp, ["remote", "add", "origin", "git@github.com:pingdotgg/t3code.git"]);
1333+
1334+ const remoteName = yield* core.ensureRemote({
1335+ cwd: tmp,
1336+ preferredName: "origin",
1337+ url: "git@github.com:pingdotgg/t3code.git/",
1338+ });
1339+
1340+ expect(remoteName).toBe("origin");
1341+ expect((yield* git(tmp, ["remote"])).split("\n").filter(Boolean)).toEqual(["origin"]);
1342+ }),
1343+ );
1344+
13261345 it.effect("reports status details and dirty state", () =>
13271346 Effect.gen(function* () {
13281347 const tmp = yield* makeTmpDir();
apps/server/src/git/Layers/GitCore.ts+1−1
@@ -106,7 +106,7 @@ function sanitizeRemoteName(value: string): string { · 105 unmodified lines
106106 }
107107
108108 function normalizeRemoteUrl(value: string): string {
109− return value.trim().replace(/\.git$/i, "").replace(/\/+$/g, "").toLowerCase();
109+ return value.trim().replace(/\/+$/g, "").replace(/\.git$/i, "").toLowerCase();
110110 }
111111
112112 function parseRemoteFetchUrls(stdout: string): Map<string, string> {
apps/server/src/git/Layers/GitHubCli.test.ts+93−87
@@ -1,5 +1,6 @@
1−import { describe, expect, it, vi } from "vitest";
2−import { Effect } from "effect";
1+import { assert, it } from "@effect/vitest";
2+import { Effect, Layer } from "effect";
3+import { afterEach, expect, vi } from "vitest";
34
45 vi.mock("../../processRunner", () => ({
56 runProcess: vi.fn(),
@@ -10,113 +11,118 @@ import { GitHubCli } from "../Services/GitHubCli.ts"; · 4 unmodified lines
1011 import { GitHubCliLive } from "./GitHubCli.ts";
1112
1213 const mockedRunProcess = vi.mocked(runProcess);
14+const layer = it.layer(Layer.empty);
1315
14−describe("GitHubCliLive", () => {
15− it("parses pull request view output", async () => {
16− mockedRunProcess.mockResolvedValueOnce({
17− stdout: JSON.stringify({
18− number: 42,
19− title: "Add PR thread creation",
20− url: "https://github.com/pingdotgg/codething-mvp/pull/42",
21− baseRefName: "main",
22− headRefName: "feature/pr-threads",
23− state: "OPEN",
24− mergedAt: null,
25− isCrossRepository: true,
26− headRepository: {
27− nameWithOwner: "octocat/codething-mvp",
28− },
29− headRepositoryOwner: {
30− login: "octocat",
31− },
32− }),
33− stderr: "",
34− code: 0,
35− signal: null,
36− timedOut: false,
37− });
16+afterEach(() => {
17+ mockedRunProcess.mockReset();
18+});
19+
20+layer("GitHubCliLive", (it) => {
21+ it.effect("parses pull request view output", () =>
22+ Effect.gen(function* () {
23+ mockedRunProcess.mockResolvedValueOnce({
24+ stdout: JSON.stringify({
25+ number: 42,
26+ title: "Add PR thread creation",
27+ url: "https://github.com/pingdotgg/codething-mvp/pull/42",
28+ baseRefName: "main",
29+ headRefName: "feature/pr-threads",
30+ state: "OPEN",
31+ mergedAt: null,
32+ isCrossRepository: true,
33+ headRepository: {
34+ nameWithOwner: "octocat/codething-mvp",
35+ },
36+ headRepositoryOwner: {
37+ login: "octocat",
38+ },
39+ }),
40+ stderr: "",
41+ code: 0,
42+ signal: null,
43+ timedOut: false,
44+ });
3845
39− const result = await Effect.runPromise(
40− Effect.gen(function* () {
46+ const result = yield* Effect.gen(function* () {
4147 const gh = yield* GitHubCli;
4248 return yield* gh.getPullRequest({
4349 cwd: "/repo",
4450 reference: "#42",
4551 });
46− }).pipe(Effect.provide(GitHubCliLive)),
47− );
52+ }).pipe(Effect.provide(GitHubCliLive));
4853
49− expect(result).toEqual({
50− number: 42,
51− title: "Add PR thread creation",
52− url: "https://github.com/pingdotgg/codething-mvp/pull/42",
53− baseRefName: "main",
54− headRefName: "feature/pr-threads",
55− state: "open",
56− isCrossRepository: true,
57− headRepositoryNameWithOwner: "octocat/codething-mvp",
58− headRepositoryOwnerLogin: "octocat",
59− });
60− expect(mockedRunProcess).toHaveBeenCalledWith(
61− "gh",
62− [
63− "pr",
64− "view",
65− "#42",
66− "--json",
67− "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner",
68− ],
69− expect.objectContaining({ cwd: "/repo" }),
70− );
71− });
54+ assert.deepStrictEqual(result, {
55+ number: 42,
56+ title: "Add PR thread creation",
57+ url: "https://github.com/pingdotgg/codething-mvp/pull/42",
58+ baseRefName: "main",
59+ headRefName: "feature/pr-threads",
60+ state: "open",
61+ isCrossRepository: true,
62+ headRepositoryNameWithOwner: "octocat/codething-mvp",
63+ headRepositoryOwnerLogin: "octocat",
64+ });
65+ expect(mockedRunProcess).toHaveBeenCalledWith(
66+ "gh",
67+ [
68+ "pr",
69+ "view",
70+ "#42",
71+ "--json",
72+ "number,title,url,baseRefName,headRefName,state,mergedAt,isCrossRepository,headRepository,headRepositoryOwner",
73+ ],
74+ expect.objectContaining({ cwd: "/repo" }),
75+ );
76+ }),
77+ );
7278
73− it("reads repository clone URLs", async () => {
74− mockedRunProcess.mockResolvedValueOnce({
75− stdout: JSON.stringify({
76− nameWithOwner: "octocat/codething-mvp",
77− url: "https://github.com/octocat/codething-mvp",
78− sshUrl: "git@github.com:octocat/codething-mvp.git",
79− }),
80− stderr: "",
81− code: 0,
82− signal: null,
83− timedOut: false,
84− });
79+ it.effect("reads repository clone URLs", () =>
80+ Effect.gen(function* () {
81+ mockedRunProcess.mockResolvedValueOnce({
82+ stdout: JSON.stringify({
83+ nameWithOwner: "octocat/codething-mvp",
84+ url: "https://github.com/octocat/codething-mvp",
85+ sshUrl: "git@github.com:octocat/codething-mvp.git",
86+ }),
87+ stderr: "",
88+ code: 0,
89+ signal: null,
90+ timedOut: false,
91+ });
8592
86− const result = await Effect.runPromise(
87− Effect.gen(function* () {
93+ const result = yield* Effect.gen(function* () {
8894 const gh = yield* GitHubCli;
8995 return yield* gh.getRepositoryCloneUrls({
9096 cwd: "/repo",
9197 repository: "octocat/codething-mvp",
9298 });
93− }).pipe(Effect.provide(GitHubCliLive)),
94− );
99+ }).pipe(Effect.provide(GitHubCliLive));
95100
96− expect(result).toEqual({
97− nameWithOwner: "octocat/codething-mvp",
98− url: "https://github.com/octocat/codething-mvp",
99− sshUrl: "git@github.com:octocat/codething-mvp.git",
100− });
101− });
101+ assert.deepStrictEqual(result, {
102+ nameWithOwner: "octocat/codething-mvp",
103+ url: "https://github.com/octocat/codething-mvp",
104+ sshUrl: "git@github.com:octocat/codething-mvp.git",
105+ });
106+ }),
107+ );
102108
103− it("surfaces a friendly error when the pull request is not found", async () => {
104− mockedRunProcess.mockRejectedValueOnce(
105− new Error(
106− "GraphQL: Could not resolve to a PullRequest with the number of 4888. (repository.pullRequest)",
107− ),
108− );
109+ it.effect("surfaces a friendly error when the pull request is not found", () =>
110+ Effect.gen(function* () {
111+ mockedRunProcess.mockRejectedValueOnce(
112+ new Error(
113+ "GraphQL: Could not resolve to a PullRequest with the number of 4888. (repository.pullRequest)",
114+ ),
115+ );
109116
110− const error = await Effect.runPromise(
111− Effect.gen(function* () {
117+ const error = yield* Effect.gen(function* () {
112118 const gh = yield* GitHubCli;
113119 return yield* gh.getPullRequest({
114120 cwd: "/repo",
115121 reference: "4888",
116122 });
117− }).pipe(Effect.provide(GitHubCliLive), Effect.flip),
118− );
123+ }).pipe(Effect.provide(GitHubCliLive), Effect.flip);
119124
120− expect(error.message).toContain("Pull request not found");
121− });
125+ assert.equal(error.message.includes("Pull request not found"), true);
126+ }),
127+ );
122128 });

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t