Skip to content

Commit 0e1a262

Browse files
authored
Merge pull request #83 from Cryptoteep/test/pull-request-coverage
test(pull-requests): cover service and mapper edge cases (→ 100%)
2 parents 6f43606 + 7cdbb5d commit 0e1a262

2 files changed

Lines changed: 319 additions & 0 deletions

File tree

‎tests/unit/modules/pull-requests/PullRequestService.test.ts‎

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,4 +212,213 @@ describe("PullRequestService", () => {
212212
expect(result.totalHours).toBeNull();
213213
});
214214
});
215+
216+
describe("create", () => {
217+
it("calls POST /repos/:owner/:repo/pulls with the mapped payload", async () => {
218+
const { service, mockRequest } = makeService();
219+
mockRequest.mockResolvedValue({
220+
data: mockPullRequestDto,
221+
status: 201,
222+
});
223+
await service.create({
224+
title: "Test PR",
225+
head: "user:feature",
226+
base: "main",
227+
maintainerCanModify: true,
228+
});
229+
expect(mockRequest).toHaveBeenCalledWith(
230+
"/repos/test-owner/test-repo/pulls",
231+
{
232+
method: "POST",
233+
body: JSON.stringify({
234+
head: "user:feature",
235+
base: "main",
236+
title: "Test PR",
237+
maintainer_can_modify: true,
238+
}),
239+
},
240+
);
241+
});
242+
243+
it("returns the mapped created PullRequest", async () => {
244+
const { service, mockRequest } = makeService();
245+
mockRequest.mockResolvedValue({
246+
data: mockPullRequestDto,
247+
status: 201,
248+
});
249+
const result = await service.create({
250+
title: "Test PR",
251+
head: "user:feature",
252+
base: "main",
253+
});
254+
expect(result.number).toBe(8);
255+
expect(result.head).toEqual({ ref: "feature", sha: "abc123" });
256+
});
257+
});
258+
259+
describe("update", () => {
260+
it("calls PATCH /repos/:owner/:repo/pulls/:number with the mapped payload", async () => {
261+
const { service, mockRequest } = makeService();
262+
mockRequest.mockResolvedValue({
263+
data: { ...mockPullRequestDto, title: "new title" },
264+
status: 200,
265+
});
266+
const result = await service.update({
267+
pullNumber: 8,
268+
title: "new title",
269+
state: "open",
270+
});
271+
expect(mockRequest).toHaveBeenCalledWith(
272+
"/repos/test-owner/test-repo/pulls/8",
273+
{
274+
method: "PATCH",
275+
body: JSON.stringify({
276+
title: "new title",
277+
state: "open",
278+
}),
279+
},
280+
);
281+
expect(result.title).toBe("new title");
282+
});
283+
});
284+
285+
describe("listCommits", () => {
286+
it("calls GET /repos/:owner/:repo/pulls/:number/commits and maps them", async () => {
287+
const { service, mockRequest } = makeService();
288+
mockRequest.mockResolvedValue({
289+
data: [
290+
{
291+
sha: "abc123",
292+
node_id: "C_1",
293+
commit: {
294+
author: {
295+
name: "Mona",
296+
email: "mona@example.com",
297+
date: "2024-01-01T00:00:00Z",
298+
},
299+
committer: {
300+
name: "Mona",
301+
email: "mona@example.com",
302+
date: "2024-01-01T00:00:00Z",
303+
},
304+
message: "feat: add thing",
305+
tree: { sha: "t1", url: "" },
306+
url: "",
307+
comment_count: 0,
308+
verification: {
309+
verified: false,
310+
reason: "unsigned",
311+
signature: null,
312+
payload: null,
313+
},
314+
},
315+
url: "",
316+
html_url: "https://github.com/owner/repo/commit/abc123",
317+
author: mockUserDto,
318+
committer: mockUserDto,
319+
parents: [],
320+
},
321+
],
322+
status: 200,
323+
});
324+
const result = await service.listCommits(8);
325+
expect(mockRequest).toHaveBeenCalledWith(
326+
"/repos/test-owner/test-repo/pulls/8/commits",
327+
);
328+
expect(result).toHaveLength(1);
329+
expect(result[0].message).toBe("feat: add thing");
330+
});
331+
});
332+
333+
describe("listFiles", () => {
334+
it("calls GET /repos/:owner/:repo/pulls/:number/files and maps them", async () => {
335+
const { service, mockRequest } = makeService();
336+
mockRequest.mockResolvedValue({
337+
data: [
338+
{
339+
sha: "f1",
340+
filename: "src/index.ts",
341+
status: "modified",
342+
additions: 1,
343+
deletions: 0,
344+
changes: 1,
345+
blob_url: "",
346+
raw_url: "",
347+
contents_url: "",
348+
patch: "@@ -1 +1 @@",
349+
},
350+
],
351+
status: 200,
352+
});
353+
const result = await service.listFiles(8);
354+
expect(mockRequest).toHaveBeenCalledWith(
355+
"/repos/test-owner/test-repo/pulls/8/files",
356+
);
357+
expect(result[0].name).toBe("src/index.ts");
358+
});
359+
});
360+
361+
describe("merge", () => {
362+
it("calls PUT /repos/:owner/:repo/pulls/:number/merge with the mapped payload", async () => {
363+
const { service, mockRequest } = makeService();
364+
const mergeResponse = {
365+
sha: "merged123",
366+
merged: true,
367+
message: "Pull Request successfully merged",
368+
};
369+
mockRequest.mockResolvedValue({ data: mergeResponse, status: 200 });
370+
const result = await service.merge({
371+
pullNumber: 8,
372+
commitTitle: "Expand docs",
373+
mergeMethod: "squash",
374+
});
375+
expect(mockRequest).toHaveBeenCalledWith(
376+
"/repos/test-owner/test-repo/pulls/8/merge",
377+
{
378+
method: "PUT",
379+
body: JSON.stringify({
380+
commit_title: "Expand docs",
381+
merge_method: "squash",
382+
}),
383+
},
384+
);
385+
expect(result).toEqual(mergeResponse);
386+
});
387+
});
388+
389+
describe("updateBranch", () => {
390+
it("calls PUT /repos/:owner/:repo/pulls/:number/update-branch", async () => {
391+
const { service, mockRequest } = makeService();
392+
const branchResponse = {
393+
message: "Updating pull request branch.",
394+
url: "https://api.github.com/repos/owner/repo/pulls/8",
395+
};
396+
mockRequest.mockResolvedValue({
397+
data: branchResponse,
398+
status: 202,
399+
});
400+
const result = await service.updateBranch(8, "abc123");
401+
expect(mockRequest).toHaveBeenCalledWith(
402+
"/repos/test-owner/test-repo/pulls/8/update-branch",
403+
{
404+
method: "PUT",
405+
body: JSON.stringify({ expected_head_sha: "abc123" }),
406+
},
407+
);
408+
expect(result).toEqual(branchResponse);
409+
});
410+
411+
it("sends an undefined expected_head_sha when omitted", async () => {
412+
const { service, mockRequest } = makeService();
413+
mockRequest.mockResolvedValue({
414+
data: { message: "ok", url: "" },
415+
status: 202,
416+
});
417+
await service.updateBranch(8);
418+
expect(mockRequest).toHaveBeenCalledWith(
419+
"/repos/test-owner/test-repo/pulls/8/update-branch",
420+
{ method: "PUT", body: JSON.stringify({}) },
421+
);
422+
});
423+
});
215424
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import {
2+
mapCreatePullRequestParams,
3+
mapMergePullRequestParams,
4+
mapPullRequestFile,
5+
mapPullRequestFiles,
6+
mapUpdatePullRequestParams,
7+
} from "../../../../src/modules/pull-requests/pull-request.mapper";
8+
import { PullRequestFileDTO } from "../../../../src/modules/pull-requests/pull-request.dto";
9+
10+
const mockFileDto: PullRequestFileDTO = {
11+
sha: "abc123",
12+
filename: "src/index.ts",
13+
status: "modified",
14+
additions: 12,
15+
deletions: 3,
16+
changes: 15,
17+
blob_url: "https://github.com/owner/repo/blob/abc123/src/index.ts",
18+
raw_url: "https://github.com/owner/repo/raw/abc123/src/index.ts",
19+
contents_url:
20+
"https://api.github.com/repos/owner/repo/contents/src/index.ts?ref=abc123",
21+
patch: "@@ -1 +1 @@",
22+
};
23+
24+
describe("pull-request.mapper", () => {
25+
describe("mapPullRequestFile", () => {
26+
it("maps filename to name and snake_case urls to camelCase", () => {
27+
const result = mapPullRequestFile(mockFileDto);
28+
expect(result.name).toBe("src/index.ts");
29+
expect(result.status).toBe("modified");
30+
expect(result.blobUrl).toBe(mockFileDto.blob_url);
31+
expect(result.rawUrl).toBe(mockFileDto.raw_url);
32+
expect(result.changes).toBe(15);
33+
});
34+
35+
it("keeps a null patch (large or binary files)", () => {
36+
const result = mapPullRequestFile({ ...mockFileDto, patch: null });
37+
expect(result.patch).toBeNull();
38+
});
39+
});
40+
41+
describe("mapPullRequestFiles", () => {
42+
it("maps an array of files", () => {
43+
const result = mapPullRequestFiles([
44+
mockFileDto,
45+
{ ...mockFileDto, filename: "README.md" },
46+
]);
47+
expect(result).toHaveLength(2);
48+
expect(result[1].name).toBe("README.md");
49+
});
50+
51+
it("maps an empty array to an empty array", () => {
52+
expect(mapPullRequestFiles([])).toEqual([]);
53+
});
54+
});
55+
56+
describe("mapCreatePullRequestParams", () => {
57+
it("maps camelCase params to the snake_case payload", () => {
58+
const result = mapCreatePullRequestParams({
59+
title: "Cool new feature",
60+
head: "user:dev",
61+
base: "main",
62+
headRepo: "user/repo",
63+
maintainerCanModify: true,
64+
draft: false,
65+
});
66+
expect(result).toEqual({
67+
title: "Cool new feature",
68+
head: "user:dev",
69+
base: "main",
70+
head_repo: "user/repo",
71+
body: undefined,
72+
maintainer_can_modify: true,
73+
draft: false,
74+
issue: undefined,
75+
});
76+
});
77+
});
78+
79+
describe("mapUpdatePullRequestParams", () => {
80+
it("maps maintainerCanModify to maintainer_can_modify", () => {
81+
const result = mapUpdatePullRequestParams({
82+
pullNumber: 8,
83+
title: "new title",
84+
state: "closed",
85+
maintainerCanModify: false,
86+
});
87+
expect(result.title).toBe("new title");
88+
expect(result.state).toBe("closed");
89+
expect(result.maintainer_can_modify).toBe(false);
90+
});
91+
});
92+
93+
describe("mapMergePullRequestParams", () => {
94+
it("maps camelCase merge params to the snake_case payload", () => {
95+
const result = mapMergePullRequestParams({
96+
pullNumber: 8,
97+
commitTitle: "Expand docs",
98+
commitMessage: "Add docs for new methods",
99+
sha: "abc123",
100+
mergeMethod: "squash",
101+
});
102+
expect(result).toEqual({
103+
commit_title: "Expand docs",
104+
commit_message: "Add docs for new methods",
105+
sha: "abc123",
106+
merge_method: "squash",
107+
});
108+
});
109+
});
110+
});

0 commit comments

Comments
 (0)