registry_test.gno
8.62 Kb · 217 lines
1package gnopm
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9const pkgA = "gno.land/p/moul/md/v1"
10
11func TestRegisterAndRead(t *testing.T) {
12 r := New()
13 uassert.Equal(t, 0, r.Size())
14 uassert.Equal(t, 0, r.Claims())
15
16 c, err := r.Register(alice, 100, pkgA, "https://github.com/moul/gno-contracts", sha1Zero, "p/moul/md", "refs/heads/main")
17 uassert.NoError(t, err)
18 uassert.Equal(t, pkgA, c.PkgPath)
19 uassert.Equal(t, int64(100), c.Height)
20 uassert.Equal(t, int64(100), c.UpdatedAt)
21 uassert.Equal(t, 1, r.Size())
22 uassert.Equal(t, 1, r.Claims())
23
24 p := r.Package(pkgA)
25 uassert.True(t, p != nil, "package exists")
26 uassert.Equal(t, 1, p.Count())
27 uassert.True(t, p.Claim(alice) != nil, "alice's claim is there")
28 uassert.True(t, p.Claim(bob) == nil, "bob has not claimed")
29 uassert.True(t, r.Package("gno.land/p/moul/nope/v0") == nil, "unclaimed path is nil")
30}
31
32// A claimant re-registering must move their own commit and nothing else, and
33// must not restart the clock: Height dates the claim, UpdatedAt dates the edit,
34// and telling them apart is how a reader spots a claim nobody has touched since
35// the first deploy.
36func TestReregisterUpdatesInPlace(t *testing.T) {
37 r := New()
38 r.Register(alice, 100, pkgA, "https://github.com/moul/gno-contracts", sha1Zero, "p/moul/md", "")
39 c, err := r.Register(alice, 250, pkgA, "https://github.com/moul/gno-contracts", sha1One, "p/moul/md", "refs/tags/v1")
40 uassert.NoError(t, err)
41
42 uassert.Equal(t, sha1One, c.Commit, "commit moved")
43 uassert.Equal(t, "refs/tags/v1", c.Ref, "ref moved")
44 uassert.Equal(t, int64(100), c.Height, "height still dates the original claim")
45 uassert.Equal(t, int64(250), c.UpdatedAt, "updated at dates the edit")
46 uassert.Equal(t, 1, r.Claims(), "still one claim, not two")
47 uassert.Equal(t, 1, r.Package(pkgA).Count())
48}
49
50// Open but tagged: a second address may claim the same path. The registry has
51// no opinion about which is true, and storing both is the point.
52func TestSeveralClaimantsCoexist(t *testing.T) {
53 r := New()
54 r.Register(alice, 100, pkgA, "https://github.com/moul/gno-contracts", sha1Zero, "p/moul/md", "")
55 r.Register(bob, 110, pkgA, "https://github.com/someone/fork", sha1One, "md", "")
56
57 p := r.Package(pkgA)
58 uassert.Equal(t, 2, p.Count())
59 uassert.Equal(t, 2, r.Claims())
60 uassert.Equal(t, 1, r.Size(), "still one package path")
61 uassert.Equal(t, sha1Zero, p.Claim(alice).Commit)
62 uassert.Equal(t, sha1One, p.Claim(bob).Commit)
63}
64
65func TestMaxClaimants(t *testing.T) {
66 r := New()
67 // Fill the path to the cap with distinct claimants.
68 base := "g1ubwjz3jmrjwmpuvrsjzgy5qkxf5wrgphzmd2t"
69 suffix := "023456789acdefgh"
70 for i := 0; i < MaxClaimants; i++ {
71 a := address(base + string(suffix[i]))
72 _, err := r.Register(a, int64(100+i), pkgA, "https://example.com/x", sha1Zero, "", "")
73 uassert.NoError(t, err, "claimant within the cap")
74 }
75 uassert.Equal(t, MaxClaimants, r.Package(pkgA).Count())
76
77 _, err := r.Register(carol, 999, pkgA, "https://example.com/x", sha1Zero, "", "")
78 uassert.ErrorIs(t, err, ErrTooManyClaimants, "one past the cap")
79
80 // An address already at the cap can still update: the cap bounds how many
81 // claimants a path carries, never how often one of them corrects itself.
82 existing := address(base + string(suffix[0]))
83 _, err = r.Register(existing, 999, pkgA, "https://example.com/x", sha1One, "", "")
84 uassert.NoError(t, err, "an existing claimant updates at the cap")
85}
86
87func TestWithdraw(t *testing.T) {
88 r := New()
89 r.Register(alice, 100, pkgA, "https://example.com/x", sha1Zero, "", "")
90 r.Register(bob, 101, pkgA, "https://example.com/y", sha1One, "", "")
91
92 uassert.ErrorIs(t, r.Withdraw(carol, pkgA), ErrClaimNotFound, "withdrawing what you never said")
93 uassert.ErrorIs(t, r.Withdraw(alice, "gno.land/p/moul/nope/v0"), ErrClaimNotFound, "unknown path")
94
95 uassert.NoError(t, r.Withdraw(alice, pkgA))
96 uassert.Equal(t, 1, r.Claims())
97 uassert.Equal(t, 1, r.Size(), "bob's claim keeps the path alive")
98 uassert.True(t, r.Package(pkgA).Claim(alice) == nil, "alice is gone")
99 uassert.True(t, r.Package(pkgA).Claim(bob) != nil, "bob is untouched")
100
101 uassert.NoError(t, r.Withdraw(bob, pkgA))
102 uassert.Equal(t, 0, r.Claims())
103 uassert.Equal(t, 0, r.Size(), "the last withdrawal removes the path")
104 uassert.True(t, r.Package(pkgA) == nil)
105}
106
107func TestRegisterRejectsInvalidInput(t *testing.T) {
108 good := func() (string, string, string, string, string) {
109 return pkgA, "https://github.com/moul/gno-contracts", sha1Zero, "p/moul/md", "refs/heads/main"
110 }
111 cases := []struct {
112 name string
113 mut func(p, repo, commit, dir, ref *string)
114 want error
115 }{
116 {"bad path", func(p, _, _, _, _ *string) { *p = "nonsense" }, ErrInvalidPkgPath},
117 {"bad repo scheme", func(_, repo, _, _, _ *string) { *repo = "javascript:alert(1)" }, ErrInvalidRepo},
118 {"abbreviated commit", func(_, _, c, _, _ *string) { *c = "0123456" }, ErrInvalidCommit},
119 {"absolute dir", func(_, _, _, d, _ *string) { *d = "/etc" }, ErrInvalidDir},
120 {"unqualified ref", func(_, _, _, _, rf *string) { *rf = "main" }, ErrInvalidRef},
121 }
122 for _, tc := range cases {
123 r := New()
124 p, repo, commit, dir, ref := good()
125 tc.mut(&p, &repo, &commit, &dir, &ref)
126 _, err := r.Register(alice, 100, p, repo, commit, dir, ref)
127 uassert.ErrorIs(t, err, tc.want, tc.name)
128 uassert.Equal(t, 0, r.Claims(), tc.name+": nothing stored")
129 }
130}
131
132func TestOwnedBy(t *testing.T) {
133 names := resolver(map[string]address{"moul": alice})
134 r := New()
135
136 c, _ := r.Register(alice, 100, pkgA, "https://example.com/x", sha1Zero, "", "")
137 uassert.True(t, c.OwnedBy(names), "the namespace holder claiming their own package")
138
139 c2, _ := r.Register(bob, 101, pkgA, "https://example.com/y", sha1One, "", "")
140 uassert.False(t, c2.OwnedBy(names), "a stranger claiming someone else's package")
141
142 // An address namespace belongs to that account with nothing registered, so
143 // it resolves with no name lookup at all.
144 own := "gno.land/r/" + addrNS + "/scratch/v0"
145 c3, err := r.Register(address(addrNS), 102, own, "https://example.com/z", sha1Zero, "", "")
146 uassert.NoError(t, err)
147 uassert.True(t, c3.OwnedBy(nil), "an address namespace needs no resolver")
148
149 c4, _ := r.Register(bob, 103, own, "https://example.com/w", sha1One, "", "")
150 uassert.False(t, c4.OwnedBy(nil), "a stranger in someone's address namespace")
151
152 // An unregistered name is held by nobody, so nobody owns it.
153 other := "gno.land/p/nobody/thing/v0"
154 c5, _ := r.Register(alice, 104, other, "https://example.com/v", sha1Zero, "", "")
155 uassert.False(t, c5.OwnedBy(names), "an unheld namespace is owned by no one")
156 uassert.False(t, c5.OwnedBy(nil), "and a nil resolver never invents ownership")
157
158 // A resolver may return an address AND report the name as not held: that is
159 // what r/sys/users does for a name whose record survives but is no longer
160 // current. The address matching is not enough, the name has to still be
161 // held, and dropping the second half of that test is a silent promotion of
162 // a stranger's claim.
163 stale := func(string) (address, bool) { return alice, false }
164 uassert.False(t, c.OwnedBy(stale), "a resolved but unheld name confers nothing")
165}
166
167func TestSourceURL(t *testing.T) {
168 r := New()
169 c, _ := r.Register(alice, 100, pkgA, "https://github.com/moul/gno-contracts", sha1Zero, "p/moul/md", "")
170 uassert.Equal(t, "https://github.com/moul/gno-contracts/tree/"+sha1Zero+"/p/moul/md", c.SourceURL())
171
172 c2, _ := r.Register(bob, 100, pkgA, "https://github.com/x/y/", sha1One, "", "")
173 uassert.Equal(t, "https://github.com/x/y/tree/"+sha1One, c2.SourceURL(), "root package, trailing slash trimmed")
174}
175
176func TestIteration(t *testing.T) {
177 r := New()
178 paths := []string{
179 "gno.land/p/alice/one/v0",
180 "gno.land/p/moul/md/v1",
181 "gno.land/p/moul/ulist/v0",
182 "gno.land/r/moul/home",
183 "gno.land/p/zed/last/v0",
184 }
185 for i, p := range paths {
186 _, err := r.Register(alice, int64(100+i), p, "https://example.com/x", sha1Zero, "", "")
187 uassert.NoError(t, err, p)
188 }
189
190 var seen []string
191 r.IteratePackages(0, 10, func(p *Package) bool {
192 seen = append(seen, p.PkgPath)
193 return false
194 })
195 uassert.Equal(t, 5, len(seen))
196 uassert.Equal(t, "gno.land/p/alice/one/v0", seen[0], "lexical order")
197 uassert.Equal(t, "gno.land/r/moul/home", seen[4], "r sorts after p")
198
199 // A namespace walk must stop at the prefix boundary rather than running to
200 // the end of the tree: "moul" is followed by "zed" and by the r/ half.
201 var ns []string
202 r.IterateNamespace("gno.land", "p", "moul", func(p *Package) bool {
203 ns = append(ns, p.PkgPath)
204 return false
205 })
206 uassert.Equal(t, 2, len(ns), "only p/moul")
207 uassert.Equal(t, "gno.land/p/moul/md/v1", ns[0])
208 uassert.Equal(t, "gno.land/p/moul/ulist/v0", ns[1])
209
210 // Stopping early stops.
211 count := 0
212 r.IteratePackages(0, 10, func(p *Package) bool {
213 count++
214 return true
215 })
216 uassert.Equal(t, 1, count, "returning true stops the walk")
217}