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.

definition_test.gno

15.08 Kb · 369 lines
  1package definition
  2
  3import (
  4	"chain"
  5	"net/url"
  6	"strconv"
  7	"strings"
  8	"testing"
  9
 10	"gno.land/p/nt/bptree/v0"
 11	"gno.land/p/nt/testutils/v0"
 12	"gno.land/p/nt/uassert/v0"
 13	"gno.land/r/akkadia/beta/admin"
 14)
 15
 16var definitionAdmin = testutils.TestAddress("definitionAdmin")
 17
 18// Call with cross(cur); each test gets fresh state and a direct admin caller.
 19func setupDefinition(cur realm, t *testing.T) {
 20	t.Helper()
 21	definitions = bptree.NewBPTree32()
 22	testing.SetOriginSend(chain.Coins{})
 23	if admin.GetAdmin() != definitionAdmin {
 24		testing.SetOriginCaller(admin.GetAdmin())
 25		testing.SetRealm(testing.NewUserRealm(admin.GetAdmin()))
 26		admin.TransferAdmin(cross(cur), definitionAdmin)
 27		testing.SetOriginCaller(definitionAdmin)
 28		testing.SetRealm(testing.NewUserRealm(definitionAdmin))
 29		admin.AcceptAdmin(cross(cur))
 30	}
 31	testing.SetOriginCaller(definitionAdmin)
 32	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
 33}
 34
 35func TestStorageMergeAndCopies(cur realm, t *testing.T) {
 36	setupDefinition(cross(cur), t)
 37	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
 38	uassert.Equal(t, 0, GetKeyCount())
 39	fields := []Field{{"z", ""}, {"name", "돌"}, {"description", " 줄\n바꿈 ` <tag> "}}
 40	setTestDefinition(cross(cur), "1101000", fields)
 41	fields[1].Value = "changed"
 42	setTestDefinition(cross(cur), "1101000", []Field{{"name", "Stone"}, {"a", "added"}})
 43	got := Get("1101000")
 44	assertFieldList(t, []Field{{"a", "added"}, {"description", " 줄\n바꿈 ` <tag> "}, {"name", "Stone"}, {"z", ""}}, got)
 45	got[0]["value"] = "mutated return"
 46	field := GetField("1101000", "a")
 47	field["value"] = "mutated field return"
 48	value, found := GetField("1101000", "a")["value"]
 49	uassert.True(t, found)
 50	uassert.Equal(t, "added", value)
 51	value, found = GetField("1101000", "z")["value"]
 52	uassert.True(t, found)
 53	uassert.Equal(t, "", value)
 54	_, found = GetField("1101000", "missing")["value"]
 55	uassert.False(t, found)
 56	uassert.Equal(t, len(Get("missing")), 0)
 57	uassert.Equal(t, 1, GetKeyCount())
 58	setTestDefinition(cross(cur), " A : 한글 ", []Field{{" 이름 ", ""}})
 59	assertStrings(t, []string{"1101000", " A : 한글 "}, ListKeys(1, 50))
 60}
 61
 62func TestDeleteAllAndReload(cur realm, t *testing.T) {
 63	setupDefinition(cross(cur), t)
 64	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
 65	batch := []testEntry{}
 66	for i := 100; i < 200; i++ {
 67		batch = append(batch, testEntry{strconv.Itoa(i), []Field{{"old", "discard"}}})
 68	}
 69	setTestDefinitions(cross(cur), batch)
 70	uassert.Equal(t, 100, GetKeyCount())
 71	DeleteAll(cross(cur))
 72	uassert.Equal(t, 0, GetKeyCount())
 73	assertStrings(t, []string{}, ListKeys(1, 50))
 74	for _, entry := range batch {
 75		uassert.Equal(t, len(Get(entry.Key)), 0)
 76		_, found := GetField(entry.Key, "old")["value"]
 77		uassert.False(t, found)
 78	}
 79	uassert.True(t, strings.Contains(Render(""), "No entries"))
 80	DeleteAll(cross(cur))
 81	setTestDefinition(cross(cur), "100", []Field{{"new", "replacement"}})
 82	fields := Get("100")
 83	assertFieldList(t, []Field{{"new", "replacement"}}, fields)
 84	uassert.Equal(t, 1, GetKeyCount())
 85}
 86
 87func TestBatchIdempotenceAndAtomicValidation(cur realm, t *testing.T) {
 88	setupDefinition(cross(cur), t)
 89	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
 90	entries := []testEntry{{"a", []Field{{"name", "old"}}}, {"b", []Field{{"name", "other"}}}}
 91	setTestDefinitions(cross(cur), entries)
 92	setTestDefinitions(cross(cur), entries)
 93	uassert.Equal(t, 2, GetKeyCount())
 94	for _, tc := range []struct {
 95		name   string
 96		entry  testEntry
 97		errMsg string
 98	}{
 99		{"duplicate entry", testEntry{"a", []Field{{"n", "v"}}}, "duplicate entry"},
100		{"duplicate field", testEntry{"c", []Field{{"n", "v"}, {"n", "v"}}}, "duplicate field key"},
101		{"invalid key", testEntry{"", []Field{{"n", "v"}}}, "empty entry token"},
102		{"invalid field", testEntry{"c", []Field{{"\n", "v"}}}, "field key is required"},
103		{"empty fields", testEntry{"c", nil}, "empty entry token"},
104	} {
105		t.Run(tc.name, func(t *testing.T) {
106			testing.SetRealm(testing.NewUserRealm(definitionAdmin))
107			batch := []testEntry{{"a", []Field{{"name", "new"}}}, tc.entry}
108			uassert.AbortsWithMessage(t, cur, tc.errMsg, func() { setTestDefinitions(cross(cur), batch) })
109			value, _ := GetField("a", "name")["value"]
110			uassert.Equal(t, "old", value)
111			uassert.Equal(t, 2, GetKeyCount())
112		})
113	}
114}
115
116func TestLimitsAndMergedRecord(cur realm, t *testing.T) {
117	setupDefinition(cross(cur), t)
118	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
119	full := []Field{}
120	for i := 0; i < 32; i++ {
121		full = append(full, Field{strconv.Itoa(i), "v"})
122	}
123	setTestDefinition(cross(cur), "full", full)
124	uassert.AbortsWithMessage(t, cur, "field count must be between 1 and 32", func() {
125		setTestDefinition(cross(cur), "full", []Field{{"extra", "v"}})
126	})
127	setTestDefinition(cross(cur), "bytes", []Field{{"a", strings.Repeat("x", 16383)}})
128	uassert.AbortsWithMessage(t, cur, "entry bytes exceeds 16384", func() {
129		setTestDefinition(cross(cur), "bytes", []Field{{"b", ""}})
130	})
131	setTestDefinition(cross(cur), "bytes", []Field{{"a", "small"}, {"b", ""}})
132	for _, tc := range []struct{ key, errMsg string }{
133		{"", "keys and values count mismatch"}, {" \t", "key is required"}, {"x\n", "invalid key"},
134		{string([]byte{255}), "invalid key"}, {strings.Repeat("가", 43), "key bytes exceeds 128"},
135	} {
136		uassert.AbortsWithMessage(t, cur, tc.errMsg, func() { setTestDefinition(cross(cur), tc.key, []Field{{"n", "v"}}) })
137	}
138	setTestDefinition(cross(cur), strings.Repeat("k", 128), []Field{{strings.Repeat("f", 128), ""}})
139	uassert.AbortsWithMessage(t, cur, "field key bytes exceeds 128", func() { setTestDefinition(cross(cur), "x", []Field{{strings.Repeat("f", 129), ""}}) })
140	uassert.AbortsWithMessage(t, cur, "invalid field value", func() { setTestDefinition(cross(cur), "x", []Field{{"n", string([]byte{255})}}) })
141	uassert.AbortsWithMessage(t, cur, "batch count must be between 1 and 100", func() { setTestDefinitions(cross(cur), nil) })
142	uassert.AbortsWithMessage(t, cur, "batch count must be between 1 and 100", func() { SetDefinitions(cross(cur), strings.Repeat("x,", 100)+"x", strings.Repeat("n=v,", 100)+"n=v") })
143	total := GetKeyCount()
144	batch := []testEntry{}
145	for i := 0; i < 5; i++ {
146		batch = append(batch, testEntry{strconv.Itoa(i), []Field{{"a", strings.Repeat("x", 16383)}}})
147	}
148	uassert.AbortsWithMessage(t, cur, "batch bytes exceeds 65536", func() { setTestDefinitions(cross(cur), batch) })
149	uassert.Equal(t, total, GetKeyCount())
150}
151
152func TestPagination(cur realm, t *testing.T) {
153	setupDefinition(cross(cur), t)
154	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
155	assertStrings(t, []string{}, ListKeys(1, 50))
156	batch := []testEntry{}
157	for i := 99; i >= 0; i-- {
158		batch = append(batch, testEntry{strconv.Itoa(i + 100), []Field{{"n", "v"}}})
159	}
160	setTestDefinitions(cross(cur), batch)
161	uassert.Equal(t, 100, GetKeyCount())
162	assertStrings(t, []string{"100", "101"}, ListKeys(1, 2))
163	assertStrings(t, []string{"198", "199"}, ListKeys(50, 2))
164	assertStrings(t, []string{}, ListKeys(51, 2))
165	assertStrings(t, []string{}, ListKeys(int(9223372036854775807), 50))
166	uassert.PanicsWithMessage(t, cur, "page must be positive", func() { ListKeys(0, 1) })
167	uassert.PanicsWithMessage(t, cur, "count must be between 1 and 50", func() { ListKeys(1, 51) })
168	keys := ListKeys(1, 1)
169	keys[0] = "changed"
170	assertStrings(t, []string{"100"}, ListKeys(1, 1))
171}
172
173func TestWriteAuthorityAndPayment(cur realm, t *testing.T) {
174	setupDefinition(cross(cur), t)
175	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
176	setTestDefinition(cross(cur), "kept", []Field{{"name", "preserved"}})
177	operator := testutils.TestAddress("definitionOperator")
178	admin.SetOperator(cross(cur), operator)
179	for _, caller := range []address{testutils.TestAddress("definitionUser"), operator} {
180		testing.SetOriginCaller(caller)
181		testing.SetRealm(testing.NewUserRealm(caller))
182		uassert.AbortsWithMessage(t, cur, "admin access required", func() { DeleteAll(cross(cur)) })
183		uassert.AbortsWithMessage(t, cur, "admin access required", func() { SetDefinitions(cross(cur), "x", "n=v") })
184	}
185	testing.SetOriginCaller(definitionAdmin)
186	testing.SetRealm(testing.NewCodeRealm("gno.land/r/example/definition_relay"))
187	uassert.AbortsWithMessage(t, cur, "admin access required", func() { DeleteAll(cross(cur)) })
188	uassert.AbortsWithMessage(t, cur, "admin access required", func() { SetDefinitions(cross(cur), "x", "n=v") })
189	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
190	testing.SetOriginSend(chain.Coins{{"ugnot", 1}})
191	uassert.AbortsWithMessage(t, cur, "attached coins not allowed", func() { DeleteAll(cross(cur)) })
192	uassert.AbortsWithMessage(t, cur, "attached coins not allowed", func() { SetDefinitions(cross(cur), "x", "n=v") })
193	testing.SetOriginSend(chain.Coins{})
194	uassert.Equal(t, 1, GetKeyCount())
195	value, found := GetField("kept", "name")["value"]
196	uassert.True(t, found)
197	uassert.Equal(t, "preserved", value)
198	stale := captureDefinitionRealm(cross(cur))
199	uassert.PanicsWithMessage(t, cur, "spoofed realm", func() { assertCanWrite(0, stale) })
200}
201
202func assertStrings(t *testing.T, expected, actual []string) {
203	t.Helper()
204	if !uassert.Equal(t, len(expected), len(actual)) {
205		return
206	}
207	for i := range expected {
208		uassert.Equal(t, expected[i], actual[i])
209	}
210}
211
212func assertFieldList(t *testing.T, expected []Field, actual []map[string]string) {
213	t.Helper()
214	if !uassert.Equal(t, len(expected), len(actual)) {
215		return
216	}
217	for i := range expected {
218		uassert.Equal(t, expected[i].Key, actual[i]["key"])
219		uassert.Equal(t, expected[i].Value, actual[i]["value"])
220	}
221}
222
223func captureDefinitionRealm(cur realm) realm { return cur }
224
225func TestSetDefinitionsAndEscapedFields(cur realm, t *testing.T) {
226	setupDefinition(cross(cur), t)
227	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
228	key := "한글 😀,+&=%;"
229	fieldKey := "field,+&=%;"
230	value := "한글 😀\n\"\\`\x00,+&=%; } //"
231	SetDefinitions(cross(cur), url.QueryEscape(key)+",1101000", url.QueryEscape(fieldKey)+"="+url.QueryEscape(value)+"&empty=,key=wooden_sword")
232	got, found := GetField(key, fieldKey)["value"]
233	uassert.True(t, found)
234	uassert.Equal(t, value, got)
235	got, found = GetField(key, "empty")["value"]
236	uassert.True(t, found)
237	uassert.Equal(t, "", got)
238	SetDefinitions(cross(cur), url.QueryEscape(key), "new=added")
239	got, _ = GetField(key, fieldKey)["value"]
240	uassert.Equal(t, value, got)
241	uassert.Equal(t, 2, GetKeyCount())
242	got, _ = GetField("1101000", "key")["value"]
243	uassert.Equal(t, "wooden_sword", got)
244	DeleteAll(cross(cur))
245	SetDefinitions(cross(cur), "1101000", "fresh=value")
246	_, found = GetField("1101000", "key")["value"]
247	uassert.False(t, found)
248}
249
250func TestSetDefinitionsRejectsInvalidInputAtomically(cur realm, t *testing.T) {
251	setupDefinition(cross(cur), t)
252	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
253	setTestDefinition(cross(cur), "kept", []Field{{"name", "old"}})
254	for _, tc := range []struct{ name, keys, values, errMsg string }{
255		{"empty", "", "", "batch count must be between 1 and 100"},
256		{"missing keys", "", "n=v", "keys and values count mismatch"},
257		{"missing values", "x", "", "keys and values count mismatch"},
258		{"short values", "kept,x", "name=new", "keys and values count mismatch"},
259		{"short keys", "kept", "name=new,n=v", "keys and values count mismatch"},
260		{"empty key", "kept,,x", "name=new,n=v,n=v", "empty entry token"},
261		{"empty fields", "kept,x", "name=new,", "empty entry token"},
262		{"trailing comma", "kept,", "name=new,", "empty entry token"},
263		{"duplicate key", "kept,k%65pt", "name=new,n=v", "duplicate entry"},
264		{"duplicate field", "kept", "name=new&n=1&%6E=2", "duplicate field key"},
265		{"invalid escape key", "kept,%zz", "name=new,n=v", "invalid encoded key"},
266		{"invalid escape value", "kept,x", "name=new,n=%zz", "invalid encoded fields"},
267		{"invalid utf8 key", "kept,%FF", "name=new,n=v", "invalid key"},
268		{"invalid utf8 value", "kept,x", "name=new,n=%FF", "invalid field value"},
269		{"invalid field", "kept,x", "name=new,%0A=v", "field key is required"},
270		{"encoded empty key", "kept,+", "name=new,n=v", "key is required"},
271		{"no fields", "kept,x", "name=new,&", "field count must be between 1 and 32"},
272		{"encoded limit", strings.Repeat("x", 131072), "n=v", "call bytes exceeds 131071"},
273		{"count limit", strings.Repeat("x,", 100) + "last", strings.Repeat("n=v,", 100) + "n=v", "batch count must be between 1 and 100"},
274		{"raw limit", "a,b,c,d,e", strings.Repeat("a="+strings.Repeat("x", 16383)+",", 4) + "a=" + strings.Repeat("x", 16383), "batch bytes exceeds 65536"},
275	} {
276		t.Run(tc.name, func(t *testing.T) {
277			testing.SetRealm(testing.NewUserRealm(definitionAdmin))
278			uassert.AbortsWithMessage(t, cur, tc.errMsg, func() { SetDefinitions(cross(cur), tc.keys, tc.values) })
279			value, found := GetField("kept", "name")["value"]
280			uassert.True(t, found)
281			uassert.Equal(t, "old", value)
282			uassert.Equal(t, 1, GetKeyCount())
283		})
284	}
285}
286
287func TestSetDefinitionsBatchAndMergedLimits(cur realm, t *testing.T) {
288	setupDefinition(cross(cur), t)
289	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
290	keys, values, fields := "", "", ""
291	for i := 0; i < 32; i++ {
292		if i > 0 {
293			fields += "&"
294		}
295		fields += strconv.Itoa(i) + "=value"
296	}
297	for i := 0; i < 100; i++ {
298		if i > 0 {
299			keys += ","
300			values += ","
301		}
302		keys += strconv.Itoa(i)
303		values += fields
304	}
305	SetDefinitions(cross(cur), keys, values)
306	uassert.Equal(t, 100, GetKeyCount())
307	uassert.AbortsWithMessage(t, cur, "field count must be between 1 and 32", func() {
308		SetDefinitions(cross(cur), "0", "extra=value")
309	})
310	got := Get("0")
311	uassert.Equal(t, 32, len(got))
312	uassert.AbortsWithMessage(t, cur, "field count must be between 1 and 32", func() {
313		SetDefinitions(cross(cur), "new", fields+"&extra=value")
314	})
315}
316
317// Test fixtures use the same encoded input boundary as the importer.
318type testEntry struct {
319	Key    string
320	Fields []Field
321}
322
323func setTestDefinition(cur realm, key string, fields []Field) {
324	setTestDefinitions(cur, []testEntry{{key, fields}})
325}
326
327func setTestDefinitions(cur realm, entries []testEntry) {
328	keys, values := "", ""
329	for i, entry := range entries {
330		if i > 0 {
331			keys += ","
332			values += ","
333		}
334		keys += url.QueryEscape(entry.Key)
335		for j, field := range entry.Fields {
336			if j > 0 {
337				values += "&"
338			}
339			values += url.QueryEscape(field.Key) + "=" + url.QueryEscape(field.Value)
340		}
341	}
342	SetDefinitions(cur, keys, values)
343}
344
345func TestNumericKeyPagination(cur realm, t *testing.T) {
346	setupDefinition(cross(cur), t)
347	testing.SetRealm(testing.NewUserRealm(definitionAdmin))
348	huge := strings.Repeat("9", 128)
349	keys := []string{"1000000", "100", "10", "2", "1", "0", "01", "00", "item:2", "item:10", "18446744073709551616", huge}
350	entries := []testEntry{}
351	for _, key := range keys {
352		entries = append(entries, testEntry{key, []Field{{"name", key}}})
353	}
354	setTestDefinitions(cross(cur), entries)
355	assertStrings(t, []string{"0", "00", "01", "1", "2"}, ListKeys(1, 5))
356	assertStrings(t, []string{"10", "100", "1000000", "18446744073709551616", huge}, ListKeys(2, 5))
357	assertStrings(t, []string{"item:10", "item:2"}, ListKeys(3, 5))
358	assertStrings(t, []string{}, ListKeys(4, 5))
359	for _, key := range keys {
360		value, found := GetField(key, "name")["value"]
361		uassert.True(t, found)
362		uassert.Equal(t, key, value)
363	}
364	SetDefinitions(cross(cur), "2", "name=updated")
365	uassert.Equal(t, len(keys), GetKeyCount())
366	assertStrings(t, []string{"0", "00", "01", "1", "2"}, ListKeys(1, 5))
367	DeleteAll(cross(cur))
368	assertStrings(t, []string{}, ListKeys(1, 5))
369}