change_test.gno
7.17 Kb · 187 lines
1package forge
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7 "gno.land/p/nt/urequire/v0"
8)
9
10// openChange sets up a repo with main at oid("a") and one open change proposing
11// oid("b"), authored by carol (a writer).
12func openChange(t *testing.T) (*Repo, *Change) {
13 t.Helper()
14 _, r := newRepo(t)
15 _, err := r.SetRef(alice, 100, "refs/heads/main", "", oid("a"), "initial")
16 urequire.NoError(t, err)
17 c, err := r.OpenChange(carol, 101, "add the log", "closes #1", "", "refs/heads/feat", oid("b"), "refs/heads/main")
18 urequire.NoError(t, err)
19 return r, c
20}
21
22func TestOpenChangeValidation(t *testing.T) {
23 _, r := newRepo(t)
24
25 _, err := r.OpenChange(eve, 101, "", "", "", "", oid("b"), "refs/heads/main")
26 uassert.ErrorIs(t, err, ErrInvalidText)
27 _, err = r.OpenChange(eve, 101, "t", "", "", "", "beef", "refs/heads/main")
28 uassert.ErrorIs(t, err, ErrInvalidOID)
29 _, err = r.OpenChange(eve, 101, "t", "", "", "", oid("b"), "main")
30 uassert.ErrorIs(t, err, ErrInvalidRefName)
31 _, err = r.OpenChange(eve, 101, "t", "", "NOT/an/id", "", oid("b"), "refs/heads/main")
32 uassert.ErrorIs(t, err, ErrInvalidRepoID)
33
34 // A fork or a plain git remote are both legal sources: the objects live
35 // off chain either way.
36 c, err := r.OpenChange(eve, 101, "from a fork", "", "eve/forge", "refs/heads/feat", oid("b"), "refs/heads/main")
37 urequire.NoError(t, err)
38 uassert.Equal(t, StateOpen, c.State)
39 c2, err := r.OpenChange(eve, 101, "from a remote", "", "https://example.com/x.git", "refs/heads/feat", oid("b"), "refs/heads/main")
40 urequire.NoError(t, err)
41 uassert.Equal(t, int64(1), c2.ID)
42 uassert.Equal(t, 2, r.OpenChangeCount())
43}
44
45func TestApprovalsAreBoundToTheReviewedObject(t *testing.T) {
46 r, c := openChange(t)
47
48 urequire.NoError(t, r.ReviewChange(bob, 102, 0, VerdictApprove, "lgtm"))
49 uassert.Equal(t, 1, r.CountApprovals(c))
50
51 // Push a new head: the approval named an object that is no longer what
52 // would be merged, so it stops counting. Nothing had to remember to
53 // dismiss it.
54 urequire.NoError(t, r.UpdateChangeHead(carol, 103, 0, oid("c")))
55 uassert.Equal(t, 0, r.CountApprovals(c))
56 uassert.True(t, c.Stale(c.Review(bob)))
57
58 urequire.NoError(t, r.ReviewChange(bob, 104, 0, VerdictApprove, "still lgtm"))
59 uassert.Equal(t, 1, r.CountApprovals(c))
60 uassert.Equal(t, 1, c.ReviewCount(), "one verdict per reviewer, latest wins")
61}
62
63func TestApprovalWeight(t *testing.T) {
64 r, c := openChange(t)
65
66 // Anyone may review; only a writer's approval counts toward policy.
67 urequire.NoError(t, r.ReviewChange(eve, 102, 0, VerdictApprove, "as a user: works"))
68 urequire.NoError(t, r.ReviewChange(dave, 102, 0, VerdictApprove, "reader here"))
69 uassert.Equal(t, 2, c.ReviewCount())
70 uassert.Equal(t, 0, r.CountApprovals(c), "unprivileged review is signal, not authority")
71
72 // The author cannot approve their own change, unless the repo says so.
73 uassert.ErrorIs(t, r.ReviewChange(carol, 102, 0, VerdictApprove, "mine"), ErrSelfApproval)
74 urequire.NoError(t, r.SetPolicy(alice, 1, true))
75 urequire.NoError(t, r.ReviewChange(carol, 102, 0, VerdictApprove, "mine"))
76 uassert.Equal(t, 1, r.CountApprovals(c))
77
78 uassert.ErrorIs(t, r.ReviewChange(bob, 102, 0, "lgtm?", ""), ErrInvalidVerdict)
79 uassert.ErrorIs(t, r.ReviewChange(bob, 102, 9, VerdictApprove, ""), ErrChangeNotFound)
80}
81
82func TestMergePolicy(t *testing.T) {
83 r, c := openChange(t)
84
85 // Default policy is one approval.
86 _, err := r.MergeChange(bob, 105, 0, oid("a"), oid("e"), "merge #0")
87 uassert.ErrorIs(t, err, ErrNotEnoughApproval)
88
89 urequire.NoError(t, r.ReviewChange(bob, 106, 0, VerdictApprove, "lgtm"))
90
91 // A writer cannot merge, however well reviewed.
92 _, err = r.MergeChange(carol, 107, 0, oid("a"), oid("e"), "")
93 uassert.ErrorIs(t, err, ErrUnauthorized)
94
95 // A blocking review stops the merge while it stands.
96 urequire.NoError(t, r.ReviewChange(alice, 107, 0, VerdictRequestChanges, "needs a test"))
97 uassert.Equal(t, 1, r.CountBlocking(c))
98 _, err = r.MergeChange(bob, 108, 0, oid("a"), oid("e"), "")
99 uassert.ErrorIs(t, err, ErrChangesRequested)
100 urequire.NoError(t, r.ReviewChange(alice, 109, 0, VerdictApprove, "test added"))
101
102 // The base moved: the change was approved against a tree that no longer
103 // exists, so the merge is refused rather than silently rebased.
104 _, err = r.MergeChange(bob, 110, 0, oid("9"), oid("e"), "")
105 uassert.ErrorIs(t, err, ErrStaleRef)
106
107 e, err := r.MergeChange(bob, 111, 0, oid("a"), oid("e"), "merge change 0")
108 urequire.NoError(t, err)
109 uassert.Equal(t, KindMerge, e.Kind)
110 uassert.Equal(t, int64(0), e.ChangeID, "the log entry names the change it came from")
111 uassert.Equal(t, oid("a"), e.OldOID)
112 uassert.Equal(t, oid("e"), r.Ref("refs/heads/main").OID, "the ref moved")
113 uassert.Equal(t, StateMerged, c.State)
114 uassert.Equal(t, bob.String(), c.MergedBy.String())
115 uassert.Equal(t, int64(111), c.MergedAt)
116 uassert.Equal(t, 0, r.OpenChangeCount())
117
118 ok, _ := r.VerifyLog()
119 uassert.True(t, ok)
120
121 // A merged change is closed to everything.
122 _, err = r.MergeChange(bob, 112, 0, oid("e"), oid("f"), "")
123 uassert.ErrorIs(t, err, ErrChangeNotOpen)
124 uassert.ErrorIs(t, r.UpdateChangeHead(carol, 112, 0, oid("d")), ErrChangeNotOpen)
125 uassert.ErrorIs(t, r.ReviewChange(bob, 112, 0, VerdictApprove, ""), ErrChangeNotOpen)
126 uassert.ErrorIs(t, r.CloseChange(carol, 112, 0), ErrChangeNotOpen)
127}
128
129func TestMergeCreatesMissingRef(t *testing.T) {
130 _, r := newRepo(t)
131 c, err := r.OpenChange(carol, 101, "first branch", "", "", "refs/heads/feat", oid("b"), "refs/heads/main")
132 urequire.NoError(t, err)
133 urequire.NoError(t, r.SetPolicy(alice, 0, false))
134
135 _, err = r.MergeChange(bob, 102, 0, oid("a"), oid("b"), "")
136 uassert.ErrorIs(t, err, ErrRefNotFound, "a target that does not exist takes no expectation")
137
138 e, err := r.MergeChange(bob, 103, 0, "", oid("b"), "first merge")
139 urequire.NoError(t, err)
140 uassert.Equal(t, KindMerge, e.Kind)
141 uassert.Equal(t, "", e.OldOID)
142 uassert.Equal(t, oid("b"), r.Ref("refs/heads/main").OID)
143 uassert.Equal(t, StateMerged, c.State)
144}
145
146func TestChangeCommentsAndClose(t *testing.T) {
147 r, c := openChange(t)
148
149 cm, err := r.CommentChange(eve, 102, 0, "does this cover the force case?")
150 urequire.NoError(t, err)
151 uassert.Equal(t, int64(0), cm.ID)
152 uassert.Equal(t, 1, c.CommentCount())
153 _, err = r.CommentChange(eve, 102, 0, "")
154 uassert.ErrorIs(t, err, ErrInvalidText)
155
156 uassert.ErrorIs(t, r.CloseChange(eve, 103, 0), ErrUnauthorized)
157 urequire.NoError(t, r.CloseChange(carol, 103, 0), "the author may withdraw")
158 uassert.Equal(t, StateClosed, c.State)
159
160 uassert.True(t, r.Change(42) == nil)
161 uassert.ErrorIs(t, r.UpdateChangeHead(carol, 104, 42, oid("d")), ErrChangeNotFound)
162}
163
164func TestChangeIterationIsNewestFirst(t *testing.T) {
165 _, r := newRepo(t)
166 for _, title := range []string{"one", "two", "three"} {
167 _, err := r.OpenChange(carol, 101, title, "", "", "", oid("b"), "refs/heads/main")
168 urequire.NoError(t, err)
169 }
170
171 var got []string
172 r.IterateChanges(0, 0, func(c *Change) bool {
173 got = append(got, c.Title)
174 return false
175 })
176 uassert.Equal(t, "three,two,one", join(got))
177
178 var reviewers []string
179 c := r.Change(0)
180 urequire.NoError(t, r.ReviewChange(bob, 102, 0, VerdictApprove, ""))
181 urequire.NoError(t, r.ReviewChange(alice, 102, 0, VerdictComment, ""))
182 c.IterateReviews(func(rv *Review) bool {
183 reviewers = append(reviewers, rv.Verdict)
184 return false
185 })
186 uassert.Equal(t, 2, len(reviewers))
187}