Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

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}