catalog_test.gno
5.92 Kb · 206 lines
1package toolbox
2
3import (
4 "testing"
5
6 "gno.land/p/nt/uassert/v0"
7)
8
9func tool(slug string, tags ...string) Tool {
10 return Tool{
11 Slug: slug,
12 Path: "gno.land/r/moul/toolbox/" + slug + "/v0",
13 Title: slug,
14 Desc: "the " + slug + " tool",
15 Tags: tags,
16 }
17}
18
19func slugs(tools []Tool) []string {
20 out := make([]string, len(tools))
21 for i, t := range tools {
22 out[i] = t.Slug
23 }
24 return out
25}
26
27// TestCatalogCopyIsAHandle is the shape bug the state pointer exists to stop.
28// With the tree embedded by value, the root is a pointer and the size an int,
29// so a write through a copy threaded the node onto the tree both of them saw
30// while incrementing only the copy's counter: a.Len() said 1 while a.Get found
31// the second entry.
32func TestCatalogCopyIsAHandle(t *testing.T) {
33 a := New()
34 uassert.NoError(t, a.Set(tool("amm")))
35
36 b := *a
37 uassert.NoError(t, b.Set(tool("usernames")))
38
39 uassert.Equal(t, 2, a.Len())
40 uassert.Equal(t, 2, b.Len())
41 uassert.Equal(t, a.Len(), len(a.List()))
42 uassert.True(t, a.Has("usernames"))
43
44 uassert.NoError(t, b.Remove("amm"))
45 uassert.Equal(t, 1, a.Len())
46 uassert.False(t, a.Has("amm"))
47}
48
49// TestCatalogCopyOfAZeroValueIsSeparate: nothing has been allocated yet, so
50// there is no directory to share. Stated because it is the one case where
51// copying does NOT alias, and a reader would otherwise have to guess.
52func TestCatalogCopyOfAZeroValueIsSeparate(t *testing.T) {
53 var a Catalog
54 b := a
55 uassert.NoError(t, b.Set(tool("usernames")))
56
57 uassert.Equal(t, 0, a.Len())
58 uassert.Equal(t, 1, b.Len())
59}
60
61func TestCatalogZeroValue(t *testing.T) {
62 var c Catalog
63 uassert.Equal(t, 0, c.Len())
64 uassert.Equal(t, 0, len(c.List()))
65 uassert.False(t, c.Has("usernames"))
66 uassert.ErrorIs(t, c.Remove("usernames"), ErrUnknownSlug)
67}
68
69// TestCatalogSortsBySlug checks the only ordering guarantee the type makes,
70// against an insertion order that is reverse, interleaved and duplicated.
71func TestCatalogSortsBySlug(t *testing.T) {
72 var c Catalog
73 for _, s := range []string{"zones", "amm", "usernames", "base64", "amm"} {
74 uassert.NoError(t, c.Set(tool(s)))
75 }
76 uassert.Equal(t, 4, c.Len())
77 want := []string{"amm", "base64", "usernames", "zones"}
78 got := slugs(c.List())
79 uassert.Equal(t, len(want), len(got))
80 for i := range want {
81 uassert.Equal(t, want[i], got[i])
82 }
83}
84
85func TestCatalogSetReplaces(t *testing.T) {
86 var c Catalog
87 uassert.NoError(t, c.Set(tool("usernames")))
88 updated := tool("usernames")
89 updated.Desc = "resolve a username or an address"
90 uassert.NoError(t, c.Set(updated))
91
92 uassert.Equal(t, 1, c.Len())
93 got, ok := c.Get("usernames")
94 uassert.True(t, ok)
95 uassert.Equal(t, "resolve a username or an address", got.Desc)
96}
97
98func TestCatalogSetRejectsInvalid(t *testing.T) {
99 var c Catalog
100 uassert.ErrorIs(t, c.Set(Tool{Path: "p", Title: "t"}), ErrEmptySlug)
101 uassert.ErrorIs(t, c.Set(Tool{Slug: "A", Path: "p", Title: "t"}), ErrBadSlug)
102 uassert.ErrorIs(t, c.Set(Tool{Slug: "a", Title: "t"}), ErrEmptyPath)
103 uassert.ErrorIs(t, c.Set(Tool{Slug: "a", Path: "p"}), ErrEmptyTitle)
104 uassert.Equal(t, 0, c.Len())
105}
106
107func TestCatalogGetAndRemove(t *testing.T) {
108 var c Catalog
109 for _, s := range []string{"amm", "base64", "usernames"} {
110 uassert.NoError(t, c.Set(tool(s)))
111 }
112
113 got, ok := c.Get("base64")
114 uassert.True(t, ok)
115 uassert.Equal(t, "gno.land/r/moul/toolbox/base64/v0", got.Path)
116
117 _, ok = c.Get("nope")
118 uassert.False(t, ok)
119
120 uassert.NoError(t, c.Remove("base64"))
121 uassert.False(t, c.Has("base64"))
122 uassert.Equal(t, 2, c.Len())
123 uassert.ErrorIs(t, c.Remove("base64"), ErrUnknownSlug)
124
125 // The two survivors are still in order around the hole.
126 got2 := slugs(c.List())
127 uassert.Equal(t, "amm", got2[0])
128 uassert.Equal(t, "usernames", got2[1])
129}
130
131// TestCatalogListIsACopy: a caller rewriting what List handed back must not
132// reach the directory through it. The struct copy covers the string fields on
133// its own; Tags is a slice, so without copyTool the header is duplicated and
134// the backing array is shared, and the write below would land in the catalog.
135func TestCatalogListIsACopy(t *testing.T) {
136 var c Catalog
137 uassert.NoError(t, c.Set(tool("usernames", "identity")))
138
139 got := c.List()
140 got[0].Title = "hijacked"
141 got[0].Tags[0] = "hijacked"
142
143 kept, ok := c.Get("usernames")
144 uassert.True(t, ok)
145 uassert.Equal(t, "usernames", kept.Title)
146 uassert.Equal(t, "identity", kept.Tags[0])
147}
148
149// TestCatalogDoesNotAliasTheCallersTags is the same hole from the other side:
150// Set must not keep a handle on the slice the caller still holds.
151func TestCatalogDoesNotAliasTheCallersTags(t *testing.T) {
152 var c Catalog
153 tags := []string{"identity"}
154 uassert.NoError(t, c.Set(Tool{
155 Slug: "usernames",
156 Path: "gno.land/r/moul/toolbox/usernames",
157 Title: "Usernames",
158 Tags: tags,
159 }))
160
161 tags[0] = "hijacked"
162
163 kept, ok := c.Get("usernames")
164 uassert.True(t, ok)
165 uassert.Equal(t, "identity", kept.Tags[0])
166 uassert.Equal(t, 1, len(c.ByTag("identity")))
167 uassert.Equal(t, 0, len(c.ByTag("hijacked")))
168}
169
170// TestCatalogGetIsACopyToo: ByTag and Get hand out the same values List does.
171func TestCatalogGetIsACopyToo(t *testing.T) {
172 var c Catalog
173 uassert.NoError(t, c.Set(tool("usernames", "identity")))
174
175 got, ok := c.Get("usernames")
176 uassert.True(t, ok)
177 got.Tags[0] = "hijacked"
178
179 byTag := c.ByTag("identity")
180 uassert.Equal(t, 1, len(byTag))
181 byTag[0].Tags[0] = "hijacked-again"
182
183 kept, _ := c.Get("usernames")
184 uassert.Equal(t, "identity", kept.Tags[0])
185}
186
187func TestCatalogByTagAndTags(t *testing.T) {
188 var c Catalog
189 uassert.NoError(t, c.Set(tool("usernames", "identity", "read")))
190 uassert.NoError(t, c.Set(tool("amm", "defi")))
191 uassert.NoError(t, c.Set(tool("whois", "identity")))
192
193 identity := slugs(c.ByTag("identity"))
194 uassert.Equal(t, 2, len(identity))
195 uassert.Equal(t, "usernames", identity[0])
196 uassert.Equal(t, "whois", identity[1])
197
198 uassert.Equal(t, 0, len(c.ByTag("nope")))
199 uassert.Equal(t, 0, len(c.ByTag("")))
200
201 tags := c.Tags()
202 uassert.Equal(t, 3, len(tags))
203 uassert.Equal(t, "defi", tags[0])
204 uassert.Equal(t, "identity", tags[1])
205 uassert.Equal(t, "read", tags[2])
206}