1
2
3
4
5
6
7
8
9
10
11
12
13 package mango
14
15 import (
16 "regexp"
17 "testing"
18
19 "gitlab.com/flimzy/testy"
20 )
21
22 func TestMatch(t *testing.T) {
23 type test struct {
24 sel Node
25 doc interface{}
26 want bool
27 }
28
29 tests := testy.NewTable()
30 tests.Add("nil selector", test{
31 sel: nil,
32 doc: "foo",
33 want: true,
34 })
35 tests.Add("equality", test{
36 sel: &conditionNode{
37 op: OpEqual,
38 cond: "foo",
39 },
40 doc: "foo",
41 want: true,
42 })
43 tests.Add("!equality", test{
44 sel: &conditionNode{
45 op: OpEqual,
46 cond: "foo",
47 },
48 doc: "bar",
49 want: false,
50 })
51 tests.Add("inequality", test{
52 sel: &conditionNode{
53 op: OpNotEqual,
54 cond: "foo",
55 },
56 doc: "bar",
57 want: true,
58 })
59 tests.Add("!inequality", test{
60 sel: &conditionNode{
61 op: OpNotEqual,
62 cond: "foo",
63 },
64 doc: "foo",
65 want: false,
66 })
67 tests.Add("less than", test{
68 sel: &conditionNode{
69 op: OpLessThan,
70 cond: float64(5),
71 },
72 doc: float64(4),
73 want: true,
74 })
75 tests.Add("!less than", test{
76 sel: &conditionNode{
77 op: OpLessThan,
78 cond: float64(5),
79 },
80 doc: float64(10),
81 want: false,
82 })
83 tests.Add("less than or equal", test{
84 sel: &conditionNode{
85 op: OpLessThanOrEqual,
86 cond: float64(5),
87 },
88 doc: float64(5),
89 want: true,
90 })
91 tests.Add("!less than or equal", test{
92 sel: &conditionNode{
93 op: OpLessThanOrEqual,
94 cond: float64(5),
95 },
96 doc: float64(8),
97 want: false,
98 })
99 tests.Add("greater than", test{
100 sel: &conditionNode{
101 op: OpGreaterThan,
102 cond: float64(5),
103 },
104 doc: float64(10),
105 want: true,
106 })
107 tests.Add("!greater than", test{
108 sel: &conditionNode{
109 op: OpGreaterThan,
110 cond: float64(5),
111 },
112 doc: float64(2),
113 want: false,
114 })
115 tests.Add("greater than or equal", test{
116 sel: &conditionNode{
117 op: OpGreaterThanOrEqual,
118 cond: float64(5),
119 },
120 doc: float64(5),
121 want: true,
122 })
123 tests.Add("!greater than or equal", test{
124 sel: &conditionNode{
125 op: OpGreaterThanOrEqual,
126 cond: float64(5),
127 },
128 doc: float64(2),
129 want: false,
130 })
131 tests.Add("exists", test{
132 sel: &fieldNode{
133 field: "foo",
134 cond: &conditionNode{op: OpExists, cond: true},
135 },
136 doc: map[string]interface{}{
137 "foo": "bar",
138 },
139 want: true,
140 })
141 tests.Add("!exists", test{
142 sel: &fieldNode{
143 field: "baz",
144 cond: &conditionNode{op: OpExists, cond: true},
145 },
146 doc: map[string]interface{}{
147 "foo": "bar",
148 },
149 want: false,
150 })
151 tests.Add("not exists", test{
152 sel: &fieldNode{
153 field: "baz",
154 cond: &conditionNode{op: OpExists, cond: false},
155 },
156 doc: map[string]interface{}{
157 "foo": "bar",
158 },
159 want: true,
160 })
161 tests.Add("!not exists", test{
162 sel: &fieldNode{
163 field: "baz",
164 cond: &conditionNode{op: OpExists, cond: true},
165 },
166 doc: map[string]interface{}{
167 "foo": "bar",
168 },
169 want: false,
170 })
171 tests.Add("type, null", test{
172 sel: &conditionNode{
173 op: OpType,
174 cond: "null",
175 },
176 doc: nil,
177 want: true,
178 })
179 tests.Add("!type, null", test{
180 sel: &conditionNode{
181 op: OpType,
182 cond: "null",
183 },
184 doc: "foo",
185 want: false,
186 })
187 tests.Add("type, boolean", test{
188 sel: &conditionNode{
189 op: OpType,
190 cond: "boolean",
191 },
192 doc: true,
193 want: true,
194 })
195 tests.Add("!type, boolean", test{
196 sel: &conditionNode{
197 op: OpType,
198 cond: "boolean",
199 },
200 doc: "foo",
201 want: false,
202 })
203 tests.Add("type, number", test{
204 sel: &conditionNode{
205 op: OpType,
206 cond: "number",
207 },
208 doc: float64(5),
209 want: true,
210 })
211 tests.Add("!type, number", test{
212 sel: &conditionNode{
213 op: OpType,
214 cond: "number",
215 },
216 doc: "foo",
217 want: false,
218 })
219 tests.Add("type, string", test{
220 sel: &conditionNode{
221 op: OpType,
222 cond: "string",
223 },
224 doc: "foo",
225 want: true,
226 })
227 tests.Add("!type, string", test{
228 sel: &conditionNode{
229 op: OpType,
230 cond: "string",
231 },
232 doc: float64(5),
233 want: false,
234 })
235 tests.Add("type, array", test{
236 sel: &conditionNode{
237 op: OpType,
238 cond: "array",
239 },
240 doc: []interface{}{"foo"},
241 want: true,
242 })
243 tests.Add("!type, array", test{
244 sel: &conditionNode{
245 op: OpType,
246 cond: "array",
247 },
248 doc: "foo",
249 want: false,
250 })
251 tests.Add("type, object", test{
252 sel: &conditionNode{
253 op: OpType,
254 cond: "object",
255 },
256 doc: map[string]interface{}{"foo": "bar"},
257 want: true,
258 })
259 tests.Add("!type, object", test{
260 sel: &conditionNode{
261 op: OpType,
262 cond: "object",
263 },
264 doc: "foo",
265 want: false,
266 })
267 tests.Add("in", test{
268 sel: &conditionNode{
269 op: OpIn,
270 cond: []interface{}{"foo", "bar"},
271 },
272 doc: "foo",
273 want: true,
274 })
275 tests.Add("!in", test{
276 sel: &conditionNode{
277 op: OpIn,
278 cond: []interface{}{"foo", "bar"},
279 },
280 doc: "baz",
281 want: false,
282 })
283 tests.Add("not in", test{
284 sel: &conditionNode{
285 op: OpNotIn,
286 cond: []interface{}{"foo", "bar"},
287 },
288 doc: "baz",
289 want: true,
290 })
291 tests.Add("!not in", test{
292 sel: &conditionNode{
293 op: OpNotIn,
294 cond: []interface{}{"foo", "bar"},
295 },
296 doc: "foo",
297 want: false,
298 })
299 tests.Add("size", test{
300 sel: &conditionNode{
301 op: OpSize,
302 cond: float64(3),
303 },
304 doc: []interface{}{"foo", "bar", "baz"},
305 want: true,
306 })
307 tests.Add("!size", test{
308 sel: &conditionNode{
309 op: OpSize,
310 cond: float64(3),
311 },
312 doc: []interface{}{"foo", "bar"},
313 want: false,
314 })
315 tests.Add("size, non-array", test{
316 sel: &conditionNode{
317 op: OpSize,
318 cond: float64(3),
319 },
320 doc: "foo",
321 want: false,
322 })
323 tests.Add("mod", test{
324 sel: &conditionNode{
325 op: OpMod,
326 cond: [2]int64{3, 2},
327 },
328 doc: float64(8),
329 want: true,
330 })
331 tests.Add("!mod", test{
332 sel: &conditionNode{
333 op: OpMod,
334 cond: [2]int64{3, 2},
335 },
336 doc: float64(7),
337 want: false,
338 })
339 tests.Add("mod, non-integer", test{
340 sel: &conditionNode{
341 op: OpMod,
342 cond: [2]int64{3, 2},
343 },
344 doc: float64(7.5),
345 want: false,
346 })
347 tests.Add("mod, non-number", test{
348 sel: &conditionNode{
349 op: OpMod,
350 cond: [2]int64{3, 2},
351 },
352 doc: "foo",
353 want: false,
354 })
355 tests.Add("regex", test{
356 sel: &conditionNode{
357 op: OpRegex,
358 cond: regexp.MustCompile("^foo$"),
359 },
360 doc: "foo",
361 want: true,
362 })
363 tests.Add("!regex", test{
364 sel: &conditionNode{
365 op: OpRegex,
366 cond: regexp.MustCompile("^foo$"),
367 },
368 doc: "bar",
369 want: false,
370 })
371 tests.Add("regexp, non-string", test{
372 sel: &conditionNode{
373 op: OpRegex,
374 cond: regexp.MustCompile("^foo$"),
375 },
376 doc: float64(5),
377 want: false,
378 })
379 tests.Add("all", test{
380 sel: &conditionNode{
381 op: OpAll,
382 cond: []interface{}{"Comedy", "Short"},
383 },
384 doc: []interface{}{
385 "Comedy",
386 "Short",
387 "Animation",
388 },
389 want: true,
390 })
391 tests.Add("!all", test{
392 sel: &conditionNode{
393 op: OpAll,
394 cond: []interface{}{"Comedy", "Short"},
395 },
396 doc: []interface{}{
397 "Comedy",
398 "Animation",
399 },
400 want: false,
401 })
402 tests.Add("all, non-array", test{
403 sel: &conditionNode{
404 op: OpAll,
405 cond: []interface{}{"Comedy", "Short"},
406 },
407 doc: "Comedy",
408 want: false,
409 })
410 tests.Add("field selector", test{
411 sel: &fieldNode{
412 field: "foo",
413 cond: &conditionNode{
414 op: OpEqual,
415 cond: "bar",
416 },
417 },
418 doc: map[string]interface{}{
419 "foo": "bar",
420 },
421 want: true,
422 })
423 tests.Add("!field selector", test{
424 sel: &fieldNode{
425 field: "asdf",
426 cond: &conditionNode{
427 op: OpEqual,
428 cond: "foo",
429 },
430 },
431 doc: map[string]interface{}{
432 "foo": "bar",
433 },
434 want: false,
435 })
436 tests.Add("field selector, non-object", test{
437 sel: &fieldNode{
438 field: "foo",
439 cond: &conditionNode{
440 op: OpEqual,
441 cond: "bar",
442 },
443 },
444 doc: "bar",
445 want: false,
446 })
447 tests.Add("field selector, nested", test{
448 sel: &fieldNode{
449 field: "foo.bar.baz",
450 cond: &conditionNode{
451 op: OpEqual,
452 cond: "hello",
453 },
454 },
455 doc: map[string]interface{}{
456 "foo": map[string]interface{}{
457 "bar": map[string]interface{}{
458 "baz": "hello",
459 },
460 },
461 },
462 want: true,
463 })
464 tests.Add("field selector, nested, non-object", test{
465 sel: &fieldNode{
466 field: "foo.bar.baz",
467 cond: &conditionNode{
468 op: OpEqual,
469 cond: "hello",
470 },
471 },
472 doc: map[string]interface{}{
473 "foo": "hello",
474 },
475 want: false,
476 })
477 tests.Add("!field selector, nested", test{
478 sel: &fieldNode{
479 field: "foo.bar.baz",
480 cond: &conditionNode{
481 op: OpEqual,
482 cond: "hello",
483 },
484 },
485 doc: map[string]interface{}{
486 "foo": map[string]interface{}{
487 "bar": map[string]interface{}{
488 "buzz": "hello",
489 },
490 },
491 },
492 want: false,
493 })
494 tests.Add("elemMatch", test{
495 sel: &fieldNode{
496 field: "foo",
497 cond: &elementNode{
498 op: OpElemMatch,
499 cond: &conditionNode{
500 op: OpEqual,
501 cond: "Horror",
502 },
503 },
504 },
505 doc: map[string]interface{}{
506 "foo": []interface{}{
507 "Comedy",
508 "Horror",
509 },
510 },
511 want: true,
512 })
513 tests.Add("!elemMatch", test{
514 sel: &fieldNode{
515 field: "genre",
516 cond: &elementNode{
517 op: OpElemMatch,
518 cond: &conditionNode{
519 op: OpEqual,
520 cond: "Horror",
521 },
522 },
523 },
524 doc: map[string]interface{}{
525 "genre": []interface{}{
526 "Comedy",
527 },
528 },
529 want: false,
530 })
531 tests.Add("elemMatch, non-array", test{
532 sel: &fieldNode{
533 field: "genre",
534 cond: &elementNode{
535 op: OpElemMatch,
536 cond: &conditionNode{
537 op: OpEqual,
538 cond: "Horror",
539 },
540 },
541 },
542 doc: map[string]interface{}{
543 "genre": "Comedy",
544 },
545 want: false,
546 })
547 tests.Add("allMatch", test{
548 sel: &fieldNode{
549 field: "genre",
550 cond: &elementNode{
551 op: OpAllMatch,
552 cond: &conditionNode{
553 op: OpEqual,
554 cond: "Horror",
555 },
556 },
557 },
558 doc: map[string]interface{}{
559 "genre": []interface{}{
560 "Horror",
561 "Horror",
562 },
563 },
564 want: true,
565 })
566 tests.Add("!allMatch", test{
567 sel: &fieldNode{
568 field: "genre",
569 cond: &elementNode{
570 op: OpAllMatch,
571 cond: &conditionNode{
572 op: OpEqual,
573 cond: "Horror",
574 },
575 },
576 },
577 doc: map[string]interface{}{
578 "genre": []interface{}{
579 "Horror",
580 "Comedy",
581 },
582 },
583 want: false,
584 })
585 tests.Add("allMatch, non-array", test{
586 sel: &fieldNode{
587 field: "genre",
588 cond: &elementNode{
589 op: OpAllMatch,
590 cond: &conditionNode{
591 op: OpEqual,
592 cond: "Horror",
593 },
594 },
595 },
596 doc: map[string]interface{}{
597 "genre": "Horror",
598 },
599 want: false,
600 })
601 tests.Add("keyMapMatch", test{
602 sel: &fieldNode{
603 field: "cameras",
604 cond: &elementNode{
605 op: OpKeyMapMatch,
606 cond: &conditionNode{
607 op: OpEqual,
608 cond: "secondary",
609 },
610 },
611 },
612 doc: map[string]interface{}{
613 "cameras": map[string]interface{}{
614 "primary": "Canon",
615 "secondary": "Nikon",
616 },
617 },
618 want: true,
619 })
620 tests.Add("!keyMapMatch", test{
621 sel: &fieldNode{
622 field: "cameras",
623 cond: &elementNode{
624 op: OpKeyMapMatch,
625 cond: &conditionNode{
626 op: OpEqual,
627 cond: "secondary",
628 },
629 },
630 },
631 doc: map[string]interface{}{
632 "cameras": map[string]interface{}{
633 "primary": "Canon",
634 },
635 },
636 want: false,
637 })
638 tests.Add("keyMapMatch, non-object", test{
639 sel: &fieldNode{
640 field: "cameras",
641 cond: &elementNode{
642 op: OpKeyMapMatch,
643 cond: &conditionNode{
644 op: OpEqual,
645 cond: "secondary",
646 },
647 },
648 },
649 doc: map[string]interface{}{
650 "cameras": []interface{}{"Canon", "Nikon"},
651 },
652 want: false,
653 })
654 tests.Add("and", test{
655 sel: &combinationNode{
656 op: OpAnd,
657 sel: []Node{
658 &fieldNode{
659 field: "foo",
660 cond: &conditionNode{
661 op: OpEqual,
662 cond: "bar",
663 },
664 },
665 &fieldNode{
666 field: "baz",
667 cond: &conditionNode{
668 op: OpEqual,
669 cond: "qux",
670 },
671 },
672 },
673 },
674 doc: map[string]interface{}{
675 "foo": "bar",
676 "baz": "qux",
677 },
678 want: true,
679 })
680 tests.Add("!and", test{
681 sel: &combinationNode{
682 op: OpAnd,
683 sel: []Node{
684 &fieldNode{
685 field: "foo",
686 cond: &conditionNode{
687 op: OpEqual,
688 cond: "bar",
689 },
690 },
691 &fieldNode{
692 field: "baz",
693 cond: &conditionNode{
694 op: OpEqual,
695 cond: "qux",
696 },
697 },
698 },
699 },
700 doc: map[string]interface{}{
701 "baz": "qux",
702 },
703 want: false,
704 })
705 tests.Add("or", test{
706 sel: &combinationNode{
707 op: OpOr,
708 sel: []Node{
709 &fieldNode{
710 field: "foo",
711 cond: &conditionNode{
712 op: OpEqual,
713 cond: "bar",
714 },
715 },
716 &fieldNode{
717 field: "baz",
718 cond: &conditionNode{
719 op: OpEqual,
720 cond: "qux",
721 },
722 },
723 },
724 },
725 doc: map[string]interface{}{
726 "foo": "bar",
727 "baz": "quux",
728 },
729 want: true,
730 })
731 tests.Add("!or", test{
732 sel: &combinationNode{
733 op: OpOr,
734 sel: []Node{
735 &fieldNode{
736 field: "foo",
737 cond: &conditionNode{
738 op: OpEqual,
739 cond: "bar",
740 },
741 },
742 &fieldNode{
743 field: "baz",
744 cond: &conditionNode{
745 op: OpEqual,
746 cond: "qux",
747 },
748 },
749 },
750 },
751 doc: map[string]interface{}{
752 "foo": "baz",
753 "baz": "quux",
754 },
755 want: false,
756 })
757 tests.Add("not", test{
758 sel: ¬Node{
759 sel: &fieldNode{
760 field: "foo",
761 cond: &conditionNode{
762 op: OpEqual,
763 cond: "bar",
764 },
765 },
766 },
767 doc: map[string]interface{}{
768 "foo": "baz",
769 },
770 want: true,
771 })
772 tests.Add("!not", test{
773 sel: ¬Node{
774 sel: &fieldNode{
775 field: "foo",
776 cond: &conditionNode{
777 op: OpEqual,
778 cond: "bar",
779 },
780 },
781 },
782 doc: map[string]interface{}{
783 "foo": "bar",
784 },
785 want: false,
786 })
787 tests.Add("nor", test{
788 sel: &combinationNode{
789 op: OpNor,
790 sel: []Node{
791 &fieldNode{
792 field: "foo",
793 cond: &conditionNode{
794 op: OpEqual,
795 cond: "bar",
796 },
797 },
798 &fieldNode{
799 field: "baz",
800 cond: &conditionNode{
801 op: OpEqual,
802 cond: "qux",
803 },
804 },
805 },
806 },
807 doc: map[string]interface{}{
808 "foo": "baz",
809 "baz": "quux",
810 },
811 want: true,
812 })
813 tests.Add("!nor", test{
814 sel: &combinationNode{
815 op: OpNor,
816 sel: []Node{
817 &fieldNode{
818 field: "foo",
819 cond: &conditionNode{
820 op: OpEqual,
821 cond: "bar",
822 },
823 },
824 &fieldNode{
825 field: "baz",
826 cond: &conditionNode{
827 op: OpEqual,
828 cond: "qux",
829 },
830 },
831 },
832 },
833 doc: map[string]interface{}{
834 "foo": "bar",
835 "baz": "quux",
836 },
837 want: false,
838 })
839
840 tests.Run(t, func(t *testing.T, tt test) {
841 got := Match(tt.sel, tt.doc)
842 if got != tt.want {
843 t.Errorf("Unexpected result: %v", got)
844 }
845 })
846 }
847
View as plain text