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}