ref_test.gno
5.45 Kb · 155 lines
1package forge
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7 "gno.land/p/nt/urequire/v0"
8)
9
10func TestSetRefCompareAndSwap(t *testing.T) {
11 _, r := newRepo(t)
12
13 // Create: the expectation is "this ref does not exist".
14 e, err := r.SetRef(carol, 101, "refs/heads/main", "", oid("a"), "initial import")
15 urequire.NoError(t, err)
16 uassert.Equal(t, KindCreate, e.Kind)
17 uassert.Equal(t, int64(0), e.Seq)
18 uassert.Equal(t, "", e.OldOID)
19 uassert.Equal(t, oid("a"), r.Ref("refs/heads/main").OID)
20 uassert.Equal(t, carol.String(), r.Ref("refs/heads/main").UpdatedBy.String())
21
22 // Creating twice is a mistake, not an overwrite.
23 _, err = r.SetRef(carol, 102, "refs/heads/main", "", oid("b"), "")
24 uassert.ErrorIs(t, err, ErrRefExists)
25
26 // A stale expectation is exactly the lost-push case --force-with-lease
27 // exists to catch, and here the lease is held by consensus.
28 _, err = r.SetRef(carol, 102, "refs/heads/main", oid("9"), oid("b"), "")
29 uassert.ErrorIs(t, err, ErrStaleRef)
30
31 // A fresh expectation succeeds and chains onto the previous entry.
32 e2, err := r.SetRef(carol, 103, "refs/heads/main", oid("a"), oid("b"), "fix the thing")
33 urequire.NoError(t, err)
34 uassert.Equal(t, KindUpdate, e2.Kind)
35 uassert.Equal(t, oid("a"), e2.OldOID)
36 uassert.Equal(t, EntryDigest(e.Digest, e2), e2.Digest, "digest commits to its predecessor")
37 uassert.Equal(t, e2.Digest, r.LogHead())
38
39 // No-op writes are rejected: they would cost a deposit and record nothing.
40 _, err = r.SetRef(carol, 104, "refs/heads/main", oid("b"), oid("b"), "")
41 uassert.ErrorIs(t, err, ErrSameOID)
42
43 // Updating a ref that does not exist is not a create.
44 _, err = r.SetRef(carol, 104, "refs/heads/other", oid("a"), oid("b"), "")
45 uassert.ErrorIs(t, err, ErrRefNotFound)
46}
47
48func TestSetRefAuthorizationAndValidation(t *testing.T) {
49 _, r := newRepo(t)
50
51 _, err := r.SetRef(eve, 101, "refs/heads/main", "", oid("a"), "")
52 uassert.ErrorIs(t, err, ErrUnauthorized, "a stranger cannot move a ref")
53 _, err = r.SetRef(dave, 101, "refs/heads/main", "", oid("a"), "")
54 uassert.ErrorIs(t, err, ErrUnauthorized, "a reader cannot move a ref")
55
56 _, err = r.SetRef(carol, 101, "main", "", oid("a"), "")
57 uassert.ErrorIs(t, err, ErrInvalidRefName)
58 _, err = r.SetRef(carol, 101, "refs/heads/main", "", "deadbeef", "")
59 uassert.ErrorIs(t, err, ErrInvalidOID)
60 _, err = r.SetRef(carol, 101, "refs/heads/main", "", oid("a"), "two\nlines")
61 uassert.ErrorIs(t, err, ErrInvalidText, "a note is one line: it is a digest field")
62}
63
64func TestForceAndDelete(t *testing.T) {
65 _, r := newRepo(t)
66 _, err := r.SetRef(carol, 101, "refs/heads/main", "", oid("a"), "initial")
67 urequire.NoError(t, err)
68 _, err = r.SetRef(carol, 102, "refs/heads/wip", "", oid("c"), "wip")
69 urequire.NoError(t, err)
70
71 // A writer cannot force. A maintainer can, and it is on the record forever.
72 _, err = r.ForceSetRef(carol, 103, "refs/heads/main", oid("d"), "rewrite")
73 uassert.ErrorIs(t, err, ErrUnauthorized)
74 e, err := r.ForceSetRef(bob, 103, "refs/heads/main", oid("d"), "drop the bad commit")
75 urequire.NoError(t, err)
76 uassert.Equal(t, KindForce, e.Kind)
77 uassert.Equal(t, oid("a"), e.OldOID, "the abandoned tip stays in the log")
78
79 // Delete is compare-and-swap too, and the default branch is not deletable.
80 _, err = r.DeleteRef(bob, 104, "refs/heads/main", oid("d"), "")
81 uassert.ErrorIs(t, err, ErrUnauthorized)
82 _, err = r.DeleteRef(bob, 104, "refs/heads/wip", oid("9"), "")
83 uassert.ErrorIs(t, err, ErrStaleRef)
84 _, err = r.DeleteRef(carol, 104, "refs/heads/wip", oid("c"), "")
85 uassert.ErrorIs(t, err, ErrUnauthorized)
86 de, err := r.DeleteRef(bob, 104, "refs/heads/wip", oid("c"), "merged")
87 urequire.NoError(t, err)
88 uassert.Equal(t, KindDelete, de.Kind)
89 uassert.Equal(t, "", de.NewOID)
90 uassert.Equal(t, 1, r.RefCount(), "the ref is gone")
91 uassert.Equal(t, 4, r.LogSize(), "the history of it is not")
92
93 _, err = r.DeleteRef(bob, 105, "refs/heads/wip", "", "")
94 uassert.ErrorIs(t, err, ErrRefNotFound)
95}
96
97func TestLogChainVerifies(t *testing.T) {
98 _, r := newRepo(t)
99 urequire.Equal(t, "", r.LogHead())
100
101 for i, o := range []string{"a", "b", "c", "d"} {
102 expected := ""
103 if i > 0 {
104 expected = r.Ref("refs/heads/main").OID
105 }
106 _, err := r.SetRef(carol, int64(100+i), "refs/heads/main", expected, oid(o), "step")
107 urequire.NoError(t, err)
108 }
109 uassert.Equal(t, 4, r.LogSize())
110
111 ok, bad := r.VerifyLog()
112 uassert.True(t, ok, "an untouched log verifies")
113 uassert.Equal(t, int64(-1), bad)
114
115 // Rewrite history the only way it can be rewritten here: reaching into the
116 // package's own memory, and the chain detects it at the doctored entry.
117 r.LogEntryAt(1).NewOID = oid("f")
118 ok, bad = r.VerifyLog()
119 uassert.False(t, ok, "a doctored entry breaks the chain")
120 uassert.Equal(t, int64(1), bad)
121}
122
123func TestLogIteration(t *testing.T) {
124 _, r := newRepo(t)
125 for i, o := range []string{"a", "b", "c"} {
126 expected := ""
127 if i > 0 {
128 expected = r.Ref("refs/heads/main").OID
129 }
130 _, err := r.SetRef(carol, int64(100+i), "refs/heads/main", expected, oid(o), "")
131 urequire.NoError(t, err)
132 }
133
134 var fwd []string
135 r.IterateLog(0, 0, func(e *LogEntry) bool {
136 fwd = append(fwd, e.NewOID[:1])
137 return false
138 })
139 uassert.Equal(t, "a,b,c", join(fwd))
140
141 var rev []string
142 r.IterateLogReverse(0, 2, func(e *LogEntry) bool {
143 rev = append(rev, e.NewOID[:1])
144 return false
145 })
146 uassert.Equal(t, "c,b", join(rev), "newest-first, paginated")
147
148 var names []string
149 r.IterateRefs(func(ref *Ref) bool {
150 names = append(names, ref.Name)
151 return false
152 })
153 uassert.Equal(t, "refs/heads/main", join(names))
154 uassert.True(t, r.LogEntryAt(99) == nil)
155}