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.

121 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[] = [], issues: 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 issues,
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())), [
42 "Add forks (#12)",
43 "Forks are ordinary repositories.",
44 ]);
45 // Listing commits is for squashes: a merge falls back to its default.
46 assert.deepEqual(defaultMessage("merge", "pull_title_and_commits", null, facts()), defaultMessage("merge", "default", null, facts()));
47});
48
49test("a squash lists its commits when it has more than one", () => {
50 const one = [commit("Add forks\n\nLong story.", "ana@example.com")];
51 assert.deepEqual(asPair(defaultMessage("squash", "default", null, facts(one))), ["Add forks (#12)", ""]);
52 const two = [commit("Add forks\n\nLong story.", "ana@example.com"), commit("Fix the test", "bo@example.com")];
53 assert.deepEqual(asPair(defaultMessage("squash", "default", null, facts(two))), ["Add forks (#12)", "* Add forks\n* Fix the test"]);
54 assert.deepEqual(asPair(defaultMessage("squash", "pull_title_and_commits", null, facts(two))), [
55 "Add forks (#12)",
56 "Add forks\n\nLong story.\n\nFix the test",
57 ]);
58 assert.equal(defaultMessage("squash", "pull_title_and_description", null, facts(two)).message, "Forks are ordinary repositories.");
59});
60
61test("a template is filled in once", () => {
62 const commits = [commit("One", "ana@example.com"), commit("Two", "Ana@Example.com"), commit("Three", "bo@example.com")];
63 const template =
64 "{title} (#{number}) into {base}\n\n{body}\nFrom {head_repo}:{branch} by {author}\n{commits}\nCloses {issues}\n{co_authors}\n{unknown} {title";
65 const { title, message } = defaultMessage("squash", "custom", template, facts(commits, [3, 4]));
66 assert.equal(title, "Add forks (#12) into main");
67 assert.equal(
68 message,
69 "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",
70 );
71 // A value holding a placeholder is not filled in again.
72 assert.equal(defaultMessage("merge", "custom", "{title}", { ...facts(), title: "{body}" }).title, "{body}");
73 // A template that fills in nothing falls back to the default.
74 assert.equal(defaultMessage("merge", "custom", "{body}", { ...facts(), body: "" }).title, "Merge pull request #12 from ana/forks");
75 assert.deepEqual(defaultMessage("squash", "custom", " ", facts()), defaultMessage("squash", "default", null, facts()));
76 assert.deepEqual(defaultMessage("squash", "custom", null, facts()), defaultMessage("squash", "default", null, facts()));
77});
78
79test("a message splits into title and body", () => {
80 assert.deepEqual(asPair(splitMessage("Title\n\n\nBody\n")), ["Title", "Body"]);
81 assert.deepEqual(asPair(splitMessage(" Title ")), ["Title", ""]);
82});
83
84test("the sample pull request previews as the server writes it", () => {
85 assert.deepEqual(asPair(defaultMessage("merge", "default", null, SAMPLE_PULL)), [
86 "Merge pull request #128 from acme/webhook-retries",
87 "Retry failed webhook deliveries",
88 ]);
89 const filled = fillTemplate("{title} (#{number})\n\n{body}\n\nCloses {issues}\n{co_authors}\n{nope}", SAMPLE_PULL);
90 assert.equal(filled.title, "Retry failed webhook deliveries (#128)");
91 assert.equal(
92 filled.message,
93 "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}",
94 );
95});
96
97test("choices from settings leave the message to the service", () => {
98 const settings = {
99 allowMergeCommit: true,
100 allowSquashMerge: true,
101 allowRebaseMerge: false,
102 allowFastForward: false,
103 defaultMergeMethod: "squash",
104 allowAutoMerge: false,
105 deleteBranchOnMerge: false,
106 suggestUpdatingBranches: true,
107 } as unknown as RepoSettings;
108 const choices = choicesFromSettings(settings, false);
109 assert.deepEqual(choices.methods, ["merge", "squash"]);
110 assert.equal(choices.defaultMethod, "squash");
111 assert.deepEqual(choices.messages, {});
112});
113
114test("the allowed methods follow the settings, fast-forward included", () => {
115 assert.deepEqual(
116 allowedMethods({ allowMergeCommit: false, allowSquashMerge: true, allowRebaseMerge: false, allowFastForward: true }),
117 ["squash", "fast_forward"],
118 );
119 assert.ok(isMergeMethod("fast_forward"));
120 assert.ok(!isMergeMethod("octopus"));
121});