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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}