internal/control/commitrefs_test.go
77 lines · 2894 bytes
1package control
2
3import (
4 "slices"
5 "strings"
6 "testing"
7
8 "gitbay.org/gitbay/internal/store"
9)
10
11// The same keyword set has to work wherever the intent is written: a
12// commit message, or a merge request title or body.
13func TestClosingRefs(t *testing.T) {
14 for _, tc := range []struct {
15 name string
16 text string
17 want []closeRef
18 }{
19 {"closes", "Closes #50", []closeRef{{"", 50}}},
20 {"lowercase and fix", "fixes #7", []closeRef{{"", 7}}},
21 {"resolved", "resolved: #12", []closeRef{{"", 12}}},
22 {"several", "Closes #1\n\nAlso fixes #2 and resolves #3", []closeRef{{"", 1}, {"", 2}, {"", 3}}},
23 {"repeats collapse", "closes #4, closes #4", []closeRef{{"", 4}}},
24 {"bare references do not close", "see #9 for context", nil},
25 {"cross-repo carries the path", "closes krz/other#3", []closeRef{{"krz/other", 3}}},
26 {"same number in two repos", "closes #3, closes krz/other#3", []closeRef{{"", 3}, {"krz/other", 3}}},
27 {"keyword must be its own word", "unclosed #5", nil},
28 } {
29 t.Run(tc.name, func(t *testing.T) {
30 got := closingRefs(tc.text)
31 slices.SortFunc(got, func(a, b closeRef) int {
32 if a.Path != b.Path {
33 return strings.Compare(a.Path, b.Path)
34 }
35 return int(a.N - b.N)
36 })
37 if !slices.Equal(got, tc.want) {
38 t.Errorf("closingRefs(%q) = %v, want %v", tc.text, got, tc.want)
39 }
40 })
41 }
42}
43
44// A merged merge request's description closes an issue in another
45// repository only when the merger holds write there. This drives the
46// same target resolution the commit path uses, without needing git.
47func TestMRDescriptionClosesAcrossRepos(t *testing.T) {
48 f := newOrgFixture(t)
49 libIssue, _ := f.st.CreateIssue(f.priv.ID, f.alice, "in priv", "", "md")
50 appIssue, _ := f.st.CreateIssue(f.app.ID, f.alice, "in app", "", "md")
51 _ = libIssue
52 _ = appIssue
53 mr := func(n int64, title string) store.MR {
54 return store.MR{Number: n, Title: title, Body: ""}
55 }
56 // carol cannot write acme/priv: the issue stays open and no comment
57 // lands.
58 ProcessMRDescription(f.st, f.app, mr(1, "Closes acme/priv#1"), f.carol)
59 if iss, _ := f.st.IssueByNumber(f.priv.ID, 1); iss.State != "open" {
60 t.Fatal("outsider closed a private repo's issue")
61 }
62 // alice can: it closes with a comment naming the source repository.
63 ProcessMRDescription(f.st, f.app, mr(2, "Closes acme/priv#1"), f.alice)
64 iss, _ := f.st.IssueByNumber(f.priv.ID, 1)
65 if iss.State != "closed" {
66 t.Fatal("writer did not close across repos")
67 }
68 comments, _ := f.st.ListIssueComments(iss.ID)
69 if len(comments) != 1 || !strings.Contains(comments[0].Body, "(/alice/app/mrs/2)") {
70 t.Fatalf("close comment = %+v", comments)
71 }
72 // An unknown path is text; a bare #N still acts in the source repo.
73 ProcessMRDescription(f.st, f.app, mr(3, "Closes nobody/nothing#1 and closes #1"), f.alice)
74 if iss, _ := f.st.IssueByNumber(f.app.ID, 1); iss.State != "closed" {
75 t.Fatal("bare #N stopped working")
76 }
77}