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

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}