Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

118 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import type { RepoSettings } from "@g1t/contracts";
5
6import {
7 type PullCommit,
8 type TemplatePull,
9 SAMPLE_PULL,
10 allowedMethods,
11 choicesFromSettings,
12 defaultMessage,
13 fillTemplate,
14 isMergeMethod,
15 splitMessage,
16} from "./merge.ts";
17
18// The same inputs as services/work/src/merge_message.rs's tests, which must
19// give the same outputs: the settings preview and the server agree.
20
21const commit = (message: string, email: string): PullCommit => ({ message, authorEmail: email });
22
23const facts = (commits: PullCommit[] = [], items: number[] = []): TemplatePull => ({
24 number: 12,
25 title: "Add forks ",
26 body: "Forks are ordinary repositories.\n",
27 branch: "forks",
28 headOwner: "ana",
29 headRepo: "ana/g1t",
30 base: "main",
31 author: "ana",
32 commits,
33 items,
34});
35
36const asPair = (text: { title: string; message: string }) => [text.title, text.message];
37
38test("a merge commit names where it came from by default", () => {
39 assert.deepEqual(asPair(defaultMessage("merge", "default", null, facts())), ["Merge pull request #12 from ana/forks", "Add forks"]);
40 assert.deepEqual(asPair(defaultMessage("merge", "pull_title", null, facts())), ["Add forks (#12)", ""]);
41 assert.deepEqual(asPair(defaultMessage("merge", "pull_title_and_description", null, facts())), ["Add forks (#12)", "Forks are ordinary repositories."]);
42 // Listing commits is for squashes: a merge falls back to its default.
43 assert.deepEqual(defaultMessage("merge", "pull_title_and_commits", null, facts()), defaultMessage("merge", "default", null, facts()));
44});
45
46test("a squash lists its commits when it has more than one", () => {
47 const one = [commit("Add forks\n\nLong story.", "ana@example.com")];
48 assert.deepEqual(asPair(defaultMessage("squash", "default", null, facts(one))), ["Add forks (#12)", ""]);
49 const two = [commit("Add forks\n\nLong story.", "ana@example.com"), commit("Fix the test", "bo@example.com")];
50 assert.deepEqual(asPair(defaultMessage("squash", "default", null, facts(two))), ["Add forks (#12)", "* Add forks\n* Fix the test"]);
51 assert.deepEqual(asPair(defaultMessage("squash", "pull_title_and_commits", null, facts(two))), [
52 "Add forks (#12)",
53 "Add forks\n\nLong story.\n\nFix the test",
54 ]);
55 assert.equal(defaultMessage("squash", "pull_title_and_description", null, facts(two)).message, "Forks are ordinary repositories.");
56});
57
58test("a template is filled in once", () => {
59 const commits = [commit("One", "ana@example.com"), commit("Two", "Ana@Example.com"), commit("Three", "bo@example.com")];
60 const template =
61 "{title} (#{number}) into {base}\n\n{body}\nFrom {head_repo}:{branch} by {author}\n{commits}\nCloses {items}\n{co_authors}\n{unknown} {title";
62 const { title, message } = defaultMessage("squash", "custom", template, facts(commits, [3, 4]));
63 assert.equal(title, "Add forks (#12) into main");
64 assert.equal(
65 message,
66 "Forks are ordinary repositories.\nFrom ana/g1t:forks by ana\n* One\n* Two\n* Three\nCloses #3, #4\nCo-authored-by: ana <ana@example.com>\nCo-authored-by: bo <bo@example.com>\n{unknown} {title",
67 );
68 // A value holding a placeholder is not filled in again.
69 assert.equal(defaultMessage("merge", "custom", "{title}", { ...facts(), title: "{body}" }).title, "{body}");
70 // A template that fills in nothing falls back to the default.
71 assert.equal(defaultMessage("merge", "custom", "{body}", { ...facts(), body: "" }).title, "Merge pull request #12 from ana/forks");
72 assert.deepEqual(defaultMessage("squash", "custom", " ", facts()), defaultMessage("squash", "default", null, facts()));
73 assert.deepEqual(defaultMessage("squash", "custom", null, facts()), defaultMessage("squash", "default", null, facts()));
74});
75
76test("a message splits into title and body", () => {
77 assert.deepEqual(asPair(splitMessage("Title\n\n\nBody\n")), ["Title", "Body"]);
78 assert.deepEqual(asPair(splitMessage(" Title ")), ["Title", ""]);
79});
80
81test("the sample pull request previews as the server writes it", () => {
82 assert.deepEqual(asPair(defaultMessage("merge", "default", null, SAMPLE_PULL)), [
83 "Merge pull request #128 from acme/webhook-retries",
84 "Retry failed webhook deliveries",
85 ]);
86 const filled = fillTemplate("{title} (#{number})\n\n{body}\n\nCloses {items}\n{co_authors}\n{nope}", SAMPLE_PULL);
87 assert.equal(filled.title, "Retry failed webhook deliveries (#128)");
88 assert.equal(
89 filled.message,
90 "Deliveries that time out are retried three times, with backoff.\n\nCloses #97\nCo-authored-by: ana <ana@example.com>\nCo-authored-by: ben <ben@example.com>\n{nope}",
91 );
92});
93
94test("choices from settings leave the message to the service", () => {
95 const settings = {
96 allowMergeCommit: true,
97 allowSquashMerge: true,
98 allowRebaseMerge: false,
99 allowFastForward: false,
100 defaultMergeMethod: "squash",
101 allowAutoMerge: false,
102 deleteBranchOnMerge: false,
103 suggestUpdatingBranches: true,
104 } as unknown as RepoSettings;
105 const choices = choicesFromSettings(settings, false);
106 assert.deepEqual(choices.methods, ["merge", "squash"]);
107 assert.equal(choices.defaultMethod, "squash");
108 assert.deepEqual(choices.messages, {});
109});
110
111test("the allowed methods follow the settings, fast-forward included", () => {
112 assert.deepEqual(allowedMethods({ allowMergeCommit: false, allowSquashMerge: true, allowRebaseMerge: false, allowFastForward: true }), [
113 "squash",
114 "fast_forward",
115 ]);
116 assert.ok(isMergeMethod("fast_forward"));
117 assert.ok(!isMergeMethod("octopus"));
118});