filter_test.mx raw
1 package filter
2
3 import (
4 "bytes"
5 "testing"
6
7 "git.smesh.lol/smesh/pkg/nostr/event"
8 "git.smesh.lol/smesh/pkg/nostr/kind"
9 "git.smesh.lol/smesh/pkg/nostr/tag"
10 "git.smesh.lol/smesh/pkg/nostr/timestamp"
11 )
12
13 // tHash returns n copies of fill as a 32-byte hash-shaped value.
14 func tHash(fill byte) (b []byte) {
15 b = []byte{:32}
16 for i := range b {
17 b[i] = fill
18 }
19 return
20 }
21
22 // tHex renders 32 copies of fill as lowercase hex.
23 func tHex(fill byte) (s []byte) {
24 tbl := []byte("0123456789abcdef")
25 hi := tbl[int32(fill>>4)]
26 lo := tbl[int32(fill&0x0F)]
27 s = []byte{:64}
28 for i := 0; i < 32; i++ {
29 s[i*2] = hi
30 s[i*2+1] = lo
31 }
32 return
33 }
34
35 func tTag(k, v string) (tt *tag.T) {
36 return tag.NewFromBytesSlice([]byte(k), []byte(v))
37 }
38
39 func tEvent(id, pub []byte, k uint16, created int64, tags *tag.S) (ev *event.E) {
40 ev = event.New()
41 ev.ID = id
42 ev.Pubkey = pub
43 ev.Kind = k
44 ev.CreatedAt = created
45 ev.Tags = tags
46 return
47 }
48
49 func TestNewFilterAndSerialize(t *testing.T) {
50 f := New()
51 if f == nil {
52 t.Fatal("New returned nil")
53 return
54 }
55 if f.Ids == nil || f.Kinds == nil || f.Authors == nil || f.Tags == nil {
56 t.Fatal("New must initialize the list fields")
57 }
58 if f.Since == nil || f.Until == nil {
59 t.Fatal("New must initialize the timestamp fields")
60 }
61 if f.Ids.Len() != 0 || f.Kinds.Len() != 0 || f.Authors.Len() != 0 || f.Tags.Len() != 0 {
62 t.Fatal("New lists must start empty")
63 }
64 if f.Limit != nil || len(f.Search) != 0 || f.Extra != nil {
65 t.Fatal("New leaves Limit, Search and Extra unset")
66 }
67 if string(f.Serialize()) != "{}" {
68 t.Fatalf("empty Serialize = %s", string(f.Serialize()))
69 }
70 if string(f.Marshal(nil)) != "{}" {
71 t.Fatalf("empty Marshal = %s", string(f.Marshal(nil)))
72 }
73 // A zero F has every field absent and must marshal the same way.
74 z := &F{}
75 if string(z.Marshal(nil)) != "{}" {
76 t.Fatalf("zero F Marshal = %s", string(z.Marshal(nil)))
77 }
78 // Sort must tolerate every list being nil.
79 z.Sort()
80 }
81
82 func TestSortOrdersAllFields(t *testing.T) {
83 f := New()
84 f.Ids = tag.NewFromBytesSlice(tHash(3), tHash(1), tHash(2))
85 f.Authors = tag.NewFromBytesSlice(tHash(9), tHash(5))
86 f.Kinds = kind.FromIntSlice([]int32{9, 1, 5})
87 f.Tags = tag.NewS(
88 tag.NewFromBytesSlice([]byte("t"), []byte("c"), []byte("a"), []byte("b")),
89 tag.NewFromBytesSlice([]byte("a"), []byte("z")),
90 )
91 f.Sort()
92 if !bytes.Equal(f.Ids.T[0], tHash(1)) || !bytes.Equal(f.Ids.T[1], tHash(2)) || !bytes.Equal(f.Ids.T[2], tHash(3)) {
93 t.Fatal("Ids not sorted ascending")
94 }
95 if !bytes.Equal(f.Authors.T[0], tHash(5)) || !bytes.Equal(f.Authors.T[1], tHash(9)) {
96 t.Fatal("Authors not sorted ascending")
97 }
98 if f.Kinds.K[0].K != 1 || f.Kinds.K[1].K != 5 || f.Kinds.K[2].K != 9 {
99 t.Fatal("Kinds not sorted ascending")
100 }
101 if !bytes.Equal(f.Tags.T[0].T[0], []byte("a")) || !bytes.Equal(f.Tags.T[1].T[0], []byte("t")) {
102 t.Fatal("Tags not sorted by key")
103 }
104 tv := f.Tags.T[1]
105 if !bytes.Equal(tv.T[0], []byte("t")) {
106 t.Fatal("tag key moved during value sort")
107 }
108 if !bytes.Equal(tv.T[1], []byte("a")) || !bytes.Equal(tv.T[2], []byte("b")) || !bytes.Equal(tv.T[3], []byte("c")) {
109 t.Fatal("tag values not sorted")
110 }
111 }
112
113 func TestMatchesIgnoringTimestampConstraints(t *testing.T) {
114 ev := tEvent(tHash(1), tHash(2), 1, 100, tag.NewS(tTag("t", "alpha"), tTag("x", "beta")))
115 nf := New()
116 if nf.MatchesIgnoringTimestampConstraints(nil) {
117 t.Fatal("nil event must not match")
118 }
119 if !nf.MatchesIgnoringTimestampConstraints(ev) {
120 t.Fatal("an empty filter matches any event")
121 }
122
123 fid := New()
124 fid.Ids = tag.NewFromBytesSlice(tHash(1))
125 if !fid.MatchesIgnoringTimestampConstraints(ev) {
126 t.Fatal("ids: exact id must match")
127 }
128 fid2 := New()
129 fid2.Ids = tag.NewFromBytesSlice(tHash(7))
130 if fid2.MatchesIgnoringTimestampConstraints(ev) {
131 t.Fatal("ids: different id must not match")
132 }
133
134 fk := New()
135 fk.Kinds = kind.FromIntSlice([]int32{1})
136 if !fk.MatchesIgnoringTimestampConstraints(ev) {
137 t.Fatal("kinds: exact kind must match")
138 }
139 fk2 := New()
140 fk2.Kinds = kind.FromIntSlice([]int32{2})
141 if fk2.MatchesIgnoringTimestampConstraints(ev) {
142 t.Fatal("kinds: different kind must not match")
143 }
144
145 fa := New()
146 fa.Authors = tag.NewFromBytesSlice(tHash(2))
147 if !fa.MatchesIgnoringTimestampConstraints(ev) {
148 t.Fatal("authors: exact author must match")
149 }
150 fa2 := New()
151 fa2.Authors = tag.NewFromBytesSlice(tHash(8))
152 if fa2.MatchesIgnoringTimestampConstraints(ev) {
153 t.Fatal("authors: different author must not match")
154 }
155
156 ft := New()
157 ft.Tags = tag.NewS(tTag("t", "alpha"))
158 if !ft.MatchesIgnoringTimestampConstraints(ev) {
159 t.Fatal("tags: exact value must match")
160 }
161 ft2 := New()
162 ft2.Tags = tag.NewS(tTag("t", "gamma"))
163 if ft2.MatchesIgnoringTimestampConstraints(ev) {
164 t.Fatal("tags: different value must not match")
165 }
166 ft3 := New()
167 ft3.Tags = tag.NewS(tTag("z", "alpha"))
168 if ft3.MatchesIgnoringTimestampConstraints(ev) {
169 t.Fatal("tags: different name must not match")
170 }
171 // A filter tag with a key but no value is skipped, not enforced.
172 ft4 := New()
173 ft4.Tags = tag.NewS(tag.NewFromBytesSlice([]byte("t")))
174 if !ft4.MatchesIgnoringTimestampConstraints(ev) {
175 t.Fatal("one-field filter tag must be skipped")
176 }
177 // With no event tags a tag constraint cannot be satisfied.
178 evNoTags := tEvent(tHash(1), tHash(2), 1, 100, nil)
179 ft5 := New()
180 ft5.Tags = tag.NewS(tTag("t", "alpha"))
181 if ft5.MatchesIgnoringTimestampConstraints(evNoTags) {
182 t.Fatal("tag constraint must fail without event tags")
183 }
184 // Timestamp fields are deliberately ignored here.
185 fstamp := New()
186 fstamp.Since = timestamp.FromUnix(1000)
187 fstamp.Until = timestamp.FromUnix(2000)
188 if !fstamp.MatchesIgnoringTimestampConstraints(ev) {
189 t.Fatal("timestamps must be ignored by this predicate")
190 }
191 }
192
193 func TestMatchesTimestampBounds(t *testing.T) {
194 ev := tEvent(tHash(1), tHash(2), 1, 100, nil)
195 f := New()
196 f.Since = timestamp.FromUnix(50)
197 if !f.Matches(ev) {
198 t.Fatal("since in the past must match")
199 }
200 f2 := New()
201 f2.Since = timestamp.FromUnix(150)
202 if f2.Matches(ev) {
203 t.Fatal("since in the future must not match")
204 }
205 f3 := New()
206 f3.Since = timestamp.FromUnix(100)
207 if !f3.Matches(ev) {
208 t.Fatal("since equal to created_at must match")
209 }
210 f4 := New()
211 f4.Until = timestamp.FromUnix(150)
212 if !f4.Matches(ev) {
213 t.Fatal("until in the future must match")
214 }
215 f5 := New()
216 f5.Until = timestamp.FromUnix(50)
217 if f5.Matches(ev) {
218 t.Fatal("until in the past must not match")
219 }
220 f6 := New()
221 f6.Until = timestamp.FromUnix(100)
222 if !f6.Matches(ev) {
223 t.Fatal("until equal to created_at must match")
224 }
225 // Zero means "absent", not "the epoch".
226 f7 := New()
227 f7.Since = timestamp.FromUnix(0)
228 f7.Until = timestamp.FromUnix(0)
229 if !f7.Matches(ev) {
230 t.Fatal("zero timestamps must be ignored")
231 }
232 // A failed content predicate wins over a satisfied time window.
233 f8 := New()
234 f8.Kinds = kind.FromIntSlice([]int32{2})
235 f8.Since = timestamp.FromUnix(50)
236 if f8.Matches(ev) {
237 t.Fatal("kind mismatch must dominate")
238 }
239 f9 := New()
240 if f9.Matches(nil) {
241 t.Fatal("nil event must not match")
242 }
243 }
244
245 func TestMarshalEachField(t *testing.T) {
246 fid := New()
247 fid.Ids = tag.NewFromBytesSlice(tHash(1))
248 wantI := "{\"ids\":[\"" | tHex(1) | "\"]}"
249 if string(fid.Marshal(nil)) != string(wantI) {
250 t.Fatalf("ids only = %s", string(fid.Marshal(nil)))
251 }
252 fk := New()
253 fk.Kinds = kind.FromIntSlice([]int32{1})
254 if string(fk.Marshal(nil)) != "{\"kinds\":[1]}" {
255 t.Fatalf("kinds only = %s", string(fk.Marshal(nil)))
256 }
257 fa := New()
258 fa.Authors = tag.NewFromBytesSlice(tHash(2))
259 wantA := "{\"authors\":[\"" | tHex(2) | "\"]}"
260 if string(fa.Marshal(nil)) != string(wantA) {
261 t.Fatalf("authors only = %s", string(fa.Marshal(nil)))
262 }
263 ft := New()
264 ft.Tags = tag.NewS(tTag("t", "v"))
265 if string(ft.Marshal(nil)) != "{\"#t\":[\"v\"]}" {
266 t.Fatalf("tags only = %s", string(ft.Marshal(nil)))
267 }
268 fs := New()
269 fs.Since = timestamp.FromUnix(5)
270 if string(fs.Marshal(nil)) != "{\"since\":5}" {
271 t.Fatalf("since only = %s", string(fs.Marshal(nil)))
272 }
273 fu := New()
274 fu.Until = timestamp.FromUnix(6)
275 if string(fu.Marshal(nil)) != "{\"until\":6}" {
276 t.Fatalf("until only = %s", string(fu.Marshal(nil)))
277 }
278 fse := New()
279 fse.Search = []byte("hi")
280 if string(fse.Marshal(nil)) != "{\"search\":\"hi\"}" {
281 t.Fatalf("search only = %s", string(fse.Marshal(nil)))
282 }
283 fl := New()
284 fl.Limit = mkU32(9)
285 if string(fl.Marshal(nil)) != "{\"limit\":9}" {
286 t.Fatalf("limit only = %s", string(fl.Marshal(nil)))
287 }
288 // A zero timestamp is omitted entirely.
289 fz := New()
290 fz.Since = timestamp.FromUnix(0)
291 fz.Until = timestamp.FromUnix(0)
292 if string(fz.Marshal(nil)) != "{}" {
293 t.Fatalf("zero timestamps must be omitted: %s", string(fz.Marshal(nil)))
294 }
295 // A non-nil destination is appended to, not replaced.
296 fp := New()
297 fp.Kinds = kind.FromIntSlice([]int32{3})
298 if string(fp.Marshal([]byte("pre"))) != "pre{\"kinds\":[3]}" {
299 t.Fatalf("Marshal into dst = %s", string(fp.Marshal([]byte("pre"))))
300 }
301 }
302
303 func TestMarshalAllFields(t *testing.T) {
304 f := New()
305 f.Ids = tag.NewFromBytesSlice(tHash(1))
306 f.Kinds = kind.FromIntSlice([]int32{1, 2})
307 f.Authors = tag.NewFromBytesSlice(tHash(2))
308 f.Tags = tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("beta"), []byte("alpha")))
309 f.Since = timestamp.FromUnix(100)
310 f.Until = timestamp.FromUnix(200)
311 f.Search = []byte("hello")
312 f.Limit = mkU32(7)
313 want := "{\"ids\":[\"" | tHex(1) | "\"],\"kinds\":[1,2],\"authors\":[\"" | tHex(2) |
314 "\"],\"#t\":[\"alpha\",\"beta\"],\"since\":100,\"until\":200,\"search\":\"hello\",\"limit\":7}"
315 if string(f.Marshal(nil)) != string(want) {
316 t.Fatalf("full marshal = %s", string(f.Marshal(nil)))
317 }
318 }
319
320 func TestMarshalTagFiltering(t *testing.T) {
321 f := New()
322 f.Tags = tag.NewS(
323 tag.NewFromBytesSlice([]byte("ab"), []byte("v")),
324 tag.NewFromBytesSlice([]byte("1"), []byte("v")),
325 tag.NewFromBytesSlice([]byte("t")),
326 tag.NewFromBytesSlice([]byte("t"), []byte("keep")),
327 )
328 // Two-character keys, non-alphabetic keys and value-less tags are all
329 // skipped; only the last tag is emitted.
330 if string(f.Marshal(nil)) != "{\"#t\":[\"keep\"]}" {
331 t.Fatalf("tag filtering = %s", string(f.Marshal(nil)))
332 }
333 // Uppercase tag keys pass the same alphabetic test.
334 fu := New()
335 fu.Tags = tag.NewS(tag.NewFromBytesSlice([]byte("E"), []byte("v")))
336 if string(fu.Marshal(nil)) != "{\"#E\":[\"v\"]}" {
337 t.Fatalf("uppercase tag key = %s", string(fu.Marshal(nil)))
338 }
339 // A tag after another emitted field takes the comma branch.
340 fc := New()
341 fc.Kinds = kind.FromIntSlice([]int32{1})
342 fc.Tags = tag.NewS(tTag("t", "v"))
343 if string(fc.Marshal(nil)) != "{\"kinds\":[1],\"#t\":[\"v\"]}" {
344 t.Fatalf("tag comma placement = %s", string(fc.Marshal(nil)))
345 }
346 // More than one value keeps the internal comma.
347 fm := New()
348 fm.Tags = tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("a"), []byte("b")))
349 if string(fm.Marshal(nil)) != "{\"#t\":[\"a\",\"b\"]}" {
350 t.Fatalf("multi-value tag = %s", string(fm.Marshal(nil)))
351 }
352 }
353
354 func TestUnmarshalAllFields(t *testing.T) {
355 in := []byte("{")
356 in = in | "\"ids\":[\""
357 in = in | tHex(1)
358 in = in | "\"],\"kinds\":[1,2],\"authors\":[\""
359 in = in | tHex(2)
360 in = in | "\"],\"#t\":[\"a\",\"b\"],\"since\":100,\"until\":200,\"search\":\"hi\",\"limit\":7}"
361 f := New()
362 rem, err := f.Unmarshal(in)
363 if err != nil {
364 t.Fatalf("Unmarshal error = %s", string(err.Error()))
365 return
366 }
367 if len(rem) != 0 {
368 t.Fatalf("remainder = %s", string(rem))
369 }
370 if f.Ids.Len() != 1 || !bytes.Equal(f.Ids.T[0], tHash(1)) {
371 t.Fatal("ids not parsed")
372 }
373 if f.Kinds.Len() != 2 || f.Kinds.K[0].K != 1 || f.Kinds.K[1].K != 2 {
374 t.Fatal("kinds not parsed")
375 }
376 if f.Authors.Len() != 1 || !bytes.Equal(f.Authors.T[0], tHash(2)) {
377 t.Fatal("authors not parsed")
378 }
379 if f.Tags.Len() != 1 {
380 t.Fatal("tag filter not parsed")
381 }
382 tg := f.Tags.T[0]
383 if tg.Len() != 3 {
384 t.Fatalf("tag field count = %d", tg.Len())
385 }
386 if !bytes.Equal(tg.T[0], []byte("t")) || !bytes.Equal(tg.T[1], []byte("a")) || !bytes.Equal(tg.T[2], []byte("b")) {
387 t.Fatal("tag fields wrong")
388 }
389 if f.Since.I64() != 100 || f.Until.I64() != 200 {
390 t.Fatal("timestamps not parsed")
391 }
392 if string(f.Search) != "hi" {
393 t.Fatalf("search = %s", string(f.Search))
394 }
395 if f.Limit == nil || *f.Limit != 7 {
396 t.Fatal("limit not parsed")
397 }
398 }
399
400 func TestUnmarshalExtraAndNested(t *testing.T) {
401 in := []byte("{\"foo\":true,\"bar\":false,\"baz\":null,\"num\":-12.5e3,")
402 in = in | "\"txt\":\"str\",\"jarr\":[1,{\"k\":2}],\"jobj\":{\"a\":[true,null]}}"
403 f := New()
404 rem, err := f.Unmarshal(in)
405 if err != nil {
406 t.Fatalf("Unmarshal error = %s", string(err.Error()))
407 return
408 }
409 if len(rem) != 0 {
410 t.Fatalf("remainder = %s", string(rem))
411 }
412 if len(f.Extra) != 7 {
413 t.Fatalf("extra key count = %d", int32(len(f.Extra)))
414 }
415 if string(f.Extra["foo"]) != "true" {
416 t.Fatalf("foo = %s", string(f.Extra["foo"]))
417 }
418 if string(f.Extra["bar"]) != "false" {
419 t.Fatalf("bar = %s", string(f.Extra["bar"]))
420 }
421 if string(f.Extra["baz"]) != "null" {
422 t.Fatalf("baz = %s", string(f.Extra["baz"]))
423 }
424 if string(f.Extra["num"]) != "-12.5e3" {
425 t.Fatalf("num = %s", string(f.Extra["num"]))
426 }
427 if string(f.Extra["txt"]) != "\"str\"" {
428 t.Fatalf("txt = %s", string(f.Extra["txt"]))
429 }
430 if string(f.Extra["jarr"]) != "[1,{\"k\":2}]" {
431 t.Fatalf("jarr = %s", string(f.Extra["jarr"]))
432 }
433 if string(f.Extra["jobj"]) != "{\"a\":[true,null]}" {
434 t.Fatalf("jobj = %s", string(f.Extra["jobj"]))
435 }
436
437 // A key longer than the initial key buffer forces mxutil.Ensure to grow.
438 f2 := New()
439 longKey := "x0123456789abcdefgh"
440 longIn := []byte("{\"") | longKey | "\":1}"
441 rem2, err2 := f2.Unmarshal(longIn)
442 if err2 != nil {
443 t.Fatalf("long key error = %s", string(err2.Error()))
444 return
445 }
446 if len(rem2) != 0 || string(f2.Extra[longKey]) != "1" {
447 t.Fatal("long unknown key not preserved")
448 }
449
450 // An unknown key that shares its leading byte with a field name used to be
451 // routed into that field's parser: "arr" ('a', shorter than "authors")
452 // errored, and "iXXX" was parsed as ids, which swallowed the rest of the
453 // filter and dropped the "kinds" that followed it. Both belong in Extra.
454 f3 := New()
455 rem3, err3 := f3.Unmarshal([]byte("{\"arr\":[1],\"s\":1,\"iXXX\":\"hello\",\"kinds\":[1]}"))
456 if err3 != nil {
457 t.Fatalf("colliding unknown key error = %s", string(err3.Error()))
458 return
459 }
460 if len(rem3) != 0 {
461 t.Fatalf("colliding remainder = %s", string(rem3))
462 }
463 if string(f3.Extra["arr"]) != "[1]" {
464 t.Fatalf("arr = %s", string(f3.Extra["arr"]))
465 }
466 if string(f3.Extra["s"]) != "1" {
467 t.Fatalf("s = %s", string(f3.Extra["s"]))
468 }
469 if string(f3.Extra["iXXX"]) != "\"hello\"" {
470 t.Fatalf("iXXX = %s", string(f3.Extra["iXXX"]))
471 }
472 if f3.Kinds == nil || f3.Kinds.Len() != 1 {
473 t.Fatal("kinds after a colliding unknown key was dropped")
474 }
475 }
476
477 func TestUnmarshalTagKeys(t *testing.T) {
478 f := New()
479 rem, err := f.Unmarshal([]byte("{\"#e\":[\"abc\"]}"))
480 if err != nil {
481 t.Fatalf("tag parse error = %s", string(err.Error()))
482 return
483 }
484 if len(rem) != 0 {
485 t.Fatalf("tag remainder = %s", string(rem))
486 }
487 if f.Tags.Len() != 1 {
488 t.Fatal("tag filter not stored")
489 }
490 tg := f.Tags.T[0]
491 if tg.Len() != 2 {
492 t.Fatalf("tag field count = %d", tg.Len())
493 }
494 if string(tg.T[0]) != "e" || string(tg.T[1]) != "abc" {
495 t.Fatal("tag fields wrong")
496 }
497
498 // Any single character after '#' is accepted at parse time.
499 f2 := New()
500 _, err2 := f2.Unmarshal([]byte("{\"#1\":[\"x\"]}"))
501 if err2 != nil {
502 t.Fatalf("#1 error = %s", string(err2.Error()))
503 return
504 }
505 if f2.Tags.Len() != 1 || string(f2.Tags.T[0].T[0]) != "1" {
506 t.Fatal("#1 not stored")
507 }
508
509 // An empty value array still produces a one-field tag.
510 f3 := New()
511 _, err3 := f3.Unmarshal([]byte("{\"#t\":[]}"))
512 if err3 != nil {
513 t.Fatalf("#t empty error = %s", string(err3.Error()))
514 return
515 }
516 if f3.Tags.Len() != 1 || f3.Tags.T[0].Len() != 1 {
517 t.Fatal("empty tag value array")
518 }
519
520 // A receiver whose Tags is nil gets one allocated.
521 f4 := &F{}
522 _, err4 := f4.Unmarshal([]byte("{\"#t\":[\"v\"]}"))
523 if err4 != nil {
524 t.Fatalf("#t nil receiver error = %s", string(err4.Error()))
525 return
526 }
527 if f4.Tags == nil || f4.Tags.Len() != 1 {
528 t.Fatal("nil Tags was not initialized")
529 }
530
531 // Distinct tag names accumulate in order.
532 f5 := New()
533 _, err5 := f5.Unmarshal([]byte("{\"#t\":[\"a\"],\"#p\":[\"b\"]}"))
534 if err5 != nil {
535 t.Fatalf("#t/#p error = %s", string(err5.Error()))
536 return
537 }
538 if f5.Tags.Len() != 2 {
539 t.Fatalf("accumulated tag count = %d", f5.Tags.Len())
540 }
541 if string(f5.Tags.T[0].T[0]) != "t" || string(f5.Tags.T[1].T[0]) != "p" {
542 t.Fatal("tag order wrong")
543 }
544
545 // A control character inside a quoted tag value aborts the parse.
546 f6 := New()
547 bad := []byte("{\"#t\":[\"a") | []byte("\n") | "\"]}"
548 _, err6 := f6.Unmarshal(bad)
549 if err6 == nil {
550 t.Fatal("control character in tag value must fail")
551 }
552 }
553
554 func TestUnmarshalWhitespaceAndSeparators(t *testing.T) {
555 // A space before the closing brace drives betweenKV's '}' branch.
556 f := New()
557 rem, err := f.Unmarshal([]byte("{\"kinds\":[1] }"))
558 if err != nil {
559 t.Fatalf("trailing space error = %s", string(err.Error()))
560 return
561 }
562 if len(rem) != 0 || f.Kinds.Len() != 1 {
563 t.Fatal("trailing space filter")
564 }
565 // A space before a comma drives betweenKV's ',' branch.
566 f2 := New()
567 rem2, err2 := f2.Unmarshal([]byte("{\"kinds\":[1] ,\"since\":5}"))
568 if err2 != nil {
569 t.Fatalf("space comma error = %s", string(err2.Error()))
570 return
571 }
572 if len(rem2) != 0 || f2.Since.I64() != 5 {
573 t.Fatal("space comma filter")
574 }
575 }
576
577 func TestUnmarshalTaintedHex(t *testing.T) {
578 Tainted = false
579 f := New()
580 rem, err := f.Unmarshal([]byte("{\"ids\":[\"abcd\"]}"))
581 if err != nil {
582 t.Fatalf("undersized id error = %s", string(err.Error()))
583 return
584 }
585 if !Tainted {
586 t.Fatal("undersized id must set Tainted")
587 }
588 if f.Ids.Len() != 0 {
589 t.Fatal("undersized id must be skipped")
590 }
591 if len(rem) != 0 {
592 t.Fatalf("id remainder = %s", string(rem))
593 }
594
595 Tainted = false
596 f2 := New()
597 _, err2 := f2.Unmarshal([]byte("{\"authors\":[\"abcd\"]}"))
598 if err2 != nil {
599 t.Fatalf("undersized author error = %s", string(err2.Error()))
600 return
601 }
602 if !Tainted {
603 t.Fatal("undersized author must set Tainted")
604 }
605 if f2.Authors.Len() != 0 {
606 t.Fatal("undersized author must be skipped")
607 }
608
609 Tainted = false
610 f3 := New()
611 good := []byte("{\"ids\":[\"") | tHex(1) | "\"]}"
612 _, err3 := f3.Unmarshal(good)
613 if err3 != nil {
614 t.Fatalf("full-length id error = %s", string(err3.Error()))
615 return
616 }
617 if Tainted {
618 t.Fatal("a full-length id must not taint")
619 }
620 if f3.Ids.Len() != 1 {
621 t.Fatal("full-length id must be stored")
622 }
623 }
624
625 func TestUnmarshalMalformed(t *testing.T) {
626 fa := New()
627 if _, e1 := fa.Unmarshal([]byte("{\"\":1}")); e1 == nil {
628 t.Fatal("empty key must fail")
629 }
630 if _, e2 := fa.Unmarshal([]byte("{")); e2 == nil {
631 t.Fatal("unterminated object must fail")
632 }
633 if _, e3 := fa.Unmarshal([]byte("no-brace")); e3 == nil {
634 t.Fatal("input without an object must fail")
635 }
636 // A key that only abbreviates or extends a field name is not that field:
637 // it is unknown, so it must land in Extra with its raw value. Testing only
638 // the first byte plus a length bound used to send these into the field
639 // parser, which errored on the short ones and mis-parsed the long ones.
640 unknownKeys := []string{"id", "k", "a", "u", "l", "s", "seabc", "sabcde", "arr", "iXXX"}
641 for _, uk := range unknownKeys {
642 fu := New()
643 uin := []byte("{\"") | uk | "\":[1]}"
644 if _, uerr := fu.Unmarshal(uin); uerr != nil {
645 t.Fatal("unknown key must parse into Extra: " | uk | " -> " | string(uerr.Error()))
646 return
647 }
648 if _, kept := fu.Extra[uk]; !kept {
649 t.Fatal("unknown key not kept in Extra: " | uk)
650 }
651 }
652 if _, e12 := fa.Unmarshal([]byte("{\"#ee\":[\"x\"]}")); e12 == nil {
653 t.Fatal("two-character tag key must fail")
654 }
655 if _, e13 := fa.Unmarshal([]byte("{\"kinds\":[x]}")); e13 == nil {
656 t.Fatal("non-numeric kind must fail")
657 }
658 if _, e14 := fa.Unmarshal([]byte("{\"ids\":[\"zz\"]}")); e14 == nil {
659 t.Fatal("invalid hex in ids must fail")
660 }
661 if _, e15 := fa.Unmarshal([]byte("{\"x\":z}")); e15 == nil {
662 t.Fatal("invalid value on an unknown key must fail")
663 }
664 if _, e16 := fa.Unmarshal([]byte("{\"authors\":[\"zz\"]}")); e16 == nil {
665 t.Fatal("invalid hex in authors must fail")
666 }
667 if _, e17 := fa.Unmarshal([]byte("{\"until\":x}")); e17 == nil {
668 t.Fatal("non-numeric until must fail")
669 }
670 if _, e18 := fa.Unmarshal([]byte("{\"limit\":x}")); e18 == nil {
671 t.Fatal("non-numeric limit must fail")
672 }
673 if _, e19 := fa.Unmarshal([]byte("{\"since\":x}")); e19 == nil {
674 t.Fatal("non-numeric since must fail")
675 }
676 if _, e20 := fa.Unmarshal([]byte("{\"search\":\"a") | []byte("\n") | "b\"}"); e20 == nil {
677 t.Fatal("control character in search must fail")
678 }
679 // A value array that runs to the end of the input returns cleanly.
680 if _, e21 := fa.Unmarshal([]byte("{\"ids\":[]")); e21 != nil {
681 t.Fatalf("truncated array error = %s", string(e21.Error()))
682 }
683 }
684
685 func TestSkipJSONValueValid(t *testing.T) {
686 type skipCase struct {
687 in string
688 val string
689 rem string
690 }
691 cases := []skipCase{
692 {"true}", "true", "}"},
693 {"false]", "false", "]"},
694 {"null,", "null", ","},
695 {"trueX", "true", "X"},
696 {"123abc", "123", "abc"},
697 {"-4.5e2,", "-4.5e2", ","},
698 {"0", "0", ""},
699 {"[\"a\",1]tail", "[\"a\",1]", "tail"},
700 {"{\"a\":1}x", "{\"a\":1}", "x"},
701 {`"abc"tail`, `"abc"`, "tail"},
702 {`"a\"b"rest`, `"a\"b"`, "rest"},
703 {`{"a":"}"}x`, `{"a":"}"}`, "x"},
704 }
705 for _, vc := range cases {
706 v, r, e := skipJSONValue([]byte(vc.in))
707 if e != nil {
708 t.Fatalf("unexpected error for %s", vc.in)
709 }
710 if string(v) != vc.val {
711 t.Fatalf("value for %s = %s", vc.in, string(v))
712 }
713 if string(r) != vc.rem {
714 t.Fatalf("remainder for %s = %s", vc.in, string(r))
715 }
716 }
717 }
718
719 func TestSkipJSONValueInvalid(t *testing.T) {
720 bad := []string{"", "tru", "truX", "fals", "nul", "z", "{", "[1", `"abc`, "]"}
721 for _, bc := range bad {
722 _, _, e := skipJSONValue([]byte(bc))
723 if e == nil {
724 t.Fatalf("skipJSONValue(%s) should fail", bc)
725 }
726 }
727 }
728
729 func TestFindMatchingBrace(t *testing.T) {
730 var e int32
731 var er error
732 e, er = findMatchingBrace([]byte("{}"), '{', '}')
733 if er != nil || e != 2 {
734 t.Fatal("empty object")
735 }
736 e, er = findMatchingBrace([]byte("[]"), '[', ']')
737 if er != nil || e != 2 {
738 t.Fatal("empty array")
739 }
740 e, er = findMatchingBrace([]byte(`{"a":{"b":2}}`), '{', '}')
741 if er != nil || e != 13 {
742 t.Fatal("nested object")
743 }
744 e, er = findMatchingBrace([]byte(`["]"]`), '[', ']')
745 if er != nil || e != 5 {
746 t.Fatal("bracket inside a string")
747 }
748 e, er = findMatchingBrace([]byte(`{"a":"}"}`), '{', '}')
749 if er != nil || e != 9 {
750 t.Fatal("brace inside a string")
751 }
752 e, er = findMatchingBrace([]byte(`{"a":"\"}"}`), '{', '}')
753 if er != nil || e != 11 {
754 t.Fatal("escaped quote must not close the string")
755 }
756 e, er = findMatchingBrace([]byte(`{"a":1`), '{', '}')
757 if er == nil {
758 t.Fatal("unterminated object must fail")
759 }
760 e, er = findMatchingBrace([]byte("[1}"), '[', ']')
761 if er == nil {
762 t.Fatal("mismatched close must fail")
763 }
764 e, er = findMatchingBrace([]byte("[1]"), '{', '}')
765 if er == nil {
766 t.Fatal("wrong opening character must fail")
767 }
768 e, er = findMatchingBrace(nil, '{', '}')
769 if er == nil {
770 t.Fatal("empty input must fail")
771 }
772 }
773
774 func TestFindClosingQuote(t *testing.T) {
775 var e int32
776 var er error
777 e, er = findClosingQuote([]byte(`"abc"rest`))
778 if er != nil || e != 5 {
779 t.Fatal("simple quoted string")
780 }
781 e, er = findClosingQuote([]byte(`"a\"b"rest`))
782 if er != nil || e != 6 {
783 t.Fatal("escaped quote")
784 }
785 e, er = findClosingQuote([]byte(`""x`))
786 if er != nil || e != 2 {
787 t.Fatal("empty quoted string")
788 }
789 e, er = findClosingQuote([]byte(`"abc`))
790 if er == nil {
791 t.Fatal("unclosed string must fail")
792 }
793 e, er = findClosingQuote([]byte("abc"))
794 if er == nil {
795 t.Fatal("input not starting with a quote must fail")
796 }
797 e, er = findClosingQuote(nil)
798 if er == nil {
799 t.Fatal("empty input must fail")
800 }
801 }
802
803 func TestScanNumber(t *testing.T) {
804 if scanNumber([]byte("123")) != 3 {
805 t.Fatal("integer scan")
806 }
807 if scanNumber([]byte("12x")) != 2 {
808 t.Fatal("trailing non-number")
809 }
810 if scanNumber([]byte("x12")) != 0 {
811 t.Fatal("leading non-number")
812 }
813 if scanNumber([]byte("-4.5e2,")) != 6 {
814 t.Fatal("exponent scan")
815 }
816 if scanNumber([]byte("0")) != 1 {
817 t.Fatal("single digit")
818 }
819 if scanNumber(nil) != 0 {
820 t.Fatal("nil input")
821 }
822 }
823
824 func TestNewSAndMatch(t *testing.T) {
825 ev := tEvent(tHash(1), tHash(2), 2, 50, nil)
826 f1 := New()
827 f1.Kinds = kind.FromIntSlice([]int32{1})
828 f2 := New()
829 f2.Kinds = kind.FromIntSlice([]int32{2})
830 s := NewS(f1, f2)
831 if s == nil || len(s.F) != 2 {
832 t.Fatal("NewS did not keep its filters")
833 }
834 if !s.Match(ev) {
835 t.Fatal("the second filter should match kind 2")
836 }
837 if s.Match(tEvent(tHash(1), tHash(2), 3, 50, nil)) {
838 t.Fatal("no filter should match kind 3")
839 }
840 if NewS().Match(ev) {
841 t.Fatal("an empty filter list matches nothing")
842 }
843 if NewS().MatchIgnoringTimestampConstraints(ev) {
844 t.Fatal("an empty filter list matches nothing ignoring time")
845 }
846 // The ignoring variant must accept what the timestamped variant rejects.
847 tf := New()
848 tf.Kinds = kind.FromIntSlice([]int32{2})
849 tf.Since = timestamp.FromUnix(1000)
850 st := NewS(tf)
851 if st.Match(ev) {
852 t.Fatal("time constraint should reject the event")
853 }
854 if !st.MatchIgnoringTimestampConstraints(ev) {
855 t.Fatal("ignoring time should accept the event")
856 }
857 if len(NewS().F) != 0 {
858 t.Fatal("empty NewS")
859 }
860 }
861
862 func TestSMarshal(t *testing.T) {
863 m1 := NewS(New(), New())
864 if string(m1.Marshal(nil)) != "[{},{}]" {
865 t.Fatalf("S.Marshal = %s", string(m1.Marshal(nil)))
866 }
867 if string(m1.Marshal([]byte("pre"))) != "pre[{},{}]" {
868 t.Fatalf("S.Marshal into dst = %s", string(m1.Marshal([]byte("pre"))))
869 }
870 if string(NewS().Marshal(nil)) != "[]" {
871 t.Fatalf("empty S.Marshal = %s", string(NewS().Marshal(nil)))
872 }
873 // Nil entries are skipped, at the front and at the back.
874 m2 := NewS()
875 var nf *F
876 m2.F = push(m2.F, nf)
877 m2.F = push(m2.F, New())
878 m2.F = push(m2.F, nf)
879 if string(m2.Marshal(nil)) != "[{}]" {
880 t.Fatalf("nil entries = %s", string(m2.Marshal(nil)))
881 }
882 // A filter with content is rendered faithfully.
883 m3 := NewS(New())
884 m3.F[0].Kinds = kind.FromIntSlice([]int32{1})
885 if string(m3.Marshal(nil)) != "[{\"kinds\":[1]}]" {
886 t.Fatalf("content S.Marshal = %s", string(m3.Marshal(nil)))
887 }
888 }
889
890 func TestUnmarshalFilters(t *testing.T) {
891 out, rem, err := UnmarshalFilters([]byte("[{\"kinds\":[1]},{\"kinds\":[2,3]}]"))
892 if err != nil {
893 t.Fatalf("array filters error = %s", string(err.Error()))
894 return
895 }
896 if len(out.F) != 2 {
897 t.Fatalf("array filter count = %d", int32(len(out.F)))
898 }
899 if len(rem) != 0 {
900 t.Fatalf("array remainder = %s", string(rem))
901 }
902 if out.F[0].Kinds.Len() != 1 || out.F[0].Kinds.K[0].K != 1 {
903 t.Fatal("first filter kinds")
904 }
905 if out.F[1].Kinds.Len() != 2 || out.F[1].Kinds.K[1].K != 3 {
906 t.Fatal("second filter kinds")
907 }
908
909 out2, rem2, err2 := UnmarshalFilters([]byte("[]"))
910 if err2 != nil {
911 t.Fatalf("empty array error = %s", string(err2.Error()))
912 return
913 }
914 if len(out2.F) != 0 || len(rem2) != 0 {
915 t.Fatal("empty array should yield no filters")
916 }
917
918 out3, rem3, err3 := UnmarshalFilters(nil)
919 if err3 != nil || len(out3.F) != 0 || rem3 != nil {
920 t.Fatal("nil input should yield nothing")
921 }
922
923 // An unwrapped single filter is accepted.
924 out4, rem4, err4 := UnmarshalFilters([]byte("{\"kinds\":[1]}"))
925 if err4 != nil {
926 t.Fatalf("unwrapped error = %s", string(err4.Error()))
927 return
928 }
929 if len(out4.F) != 1 || len(rem4) != 0 {
930 t.Fatal("unwrapped single filter")
931 }
932
933 // Several unwrapped filters separated by commas.
934 out5, rem5, err5 := UnmarshalFilters([]byte("{\"kinds\":[1]},{\"kinds\":[2]}"))
935 if err5 != nil {
936 t.Fatalf("unwrapped list error = %s", string(err5.Error()))
937 return
938 }
939 if len(out5.F) != 2 || len(rem5) != 0 {
940 t.Fatalf("unwrapped list count = %d", int32(len(out5.F)))
941 }
942
943 // A trailing ']' on an unwrapped list is left in the remainder.
944 out6, rem6, err6 := UnmarshalFilters([]byte("{\"kinds\":[1]}]"))
945 if err6 != nil {
946 t.Fatalf("unwrapped trailing bracket error = %s", string(err6.Error()))
947 return
948 }
949 if len(out6.F) != 1 || string(rem6) != "]" {
950 t.Fatalf("unwrapped trailing remainder = %s", string(rem6))
951 }
952
953 // Junk after a filter is an error.
954 _, _, err7 := UnmarshalFilters([]byte("[{\"kinds\":[1]}x]"))
955 if err7 == nil {
956 t.Fatal("junk after a filter must fail")
957 }
958 // A filter that fails to parse propagates its error.
959 _, _, err8 := UnmarshalFilters([]byte("[{\"\":1}]"))
960 if err8 == nil {
961 t.Fatal("a malformed inner filter must fail")
962 }
963 // A trailing comma ends the loop with nothing left to parse.
964 out9, rem9, err9 := UnmarshalFilters([]byte("[{\"kinds\":[1]},"))
965 if err9 != nil {
966 t.Fatalf("trailing comma error = %s", string(err9.Error()))
967 return
968 }
969 if len(out9.F) != 1 || len(rem9) != 0 {
970 t.Fatal("trailing comma should keep the parsed filter")
971 }
972 // More filters than the initial Ensure batch forces the filter slice to
973 // grow a second time, exercising the copy path.
974 many := []byte("[")
975 for i := 0; i < 18; i++ {
976 if i > 0 {
977 many = many | ","
978 }
979 many = many | "{\"kinds\":[1]}"
980 }
981 many = many | "]"
982 outM, remM, errM := UnmarshalFilters(many)
983 if errM != nil {
984 t.Fatalf("many filters error = %s", string(errM.Error()))
985 return
986 }
987 if len(outM.F) != 18 || len(remM) != 0 {
988 t.Fatalf("many filter count = %d", int32(len(outM.F)))
989 }
990 }
991