github.com/ipld/go-ipld-prime@v0.21.0/traversal/selector/matcher_test.go (about)

     1  package selector_test
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"regexp"
     7  	"testing"
     8  
     9  	qt "github.com/frankban/quicktest"
    10  
    11  	"github.com/ipld/go-ipld-prime/datamodel"
    12  	"github.com/ipld/go-ipld-prime/fluent/qp"
    13  	"github.com/ipld/go-ipld-prime/node/basicnode"
    14  	"github.com/ipld/go-ipld-prime/testutil"
    15  	"github.com/ipld/go-ipld-prime/traversal"
    16  	"github.com/ipld/go-ipld-prime/traversal/selector"
    17  )
    18  
    19  func TestSubsetMatch(t *testing.T) {
    20  	expectedString := "foobarbaz!"
    21  	nodes := []struct {
    22  		name string
    23  		node datamodel.Node
    24  	}{
    25  		{"stringNode", basicnode.NewString(expectedString)},
    26  		{"bytesNode", testutil.NewSimpleBytes([]byte(expectedString))},
    27  		{"largeBytesNode", testutil.NewMultiByteNode(
    28  			[]byte("foo"),
    29  			[]byte("bar"),
    30  			[]byte("baz"),
    31  			[]byte("!"),
    32  		)},
    33  	}
    34  
    35  	// selector for a slice of the value of the "bipbop" field within a map
    36  	mkRangeSelector := func(from int64, to int64) (datamodel.Node, error) {
    37  		return qp.BuildMap(basicnode.Prototype.Map, 1, func(na datamodel.MapAssembler) {
    38  			qp.MapEntry(na, selector.SelectorKey_ExploreFields, qp.Map(1, func(na datamodel.MapAssembler) {
    39  				qp.MapEntry(na, selector.SelectorKey_Fields, qp.Map(1, func(na datamodel.MapAssembler) {
    40  					qp.MapEntry(na, "bipbop", qp.Map(1, func(na datamodel.MapAssembler) {
    41  						qp.MapEntry(na, selector.SelectorKey_Matcher, qp.Map(1, func(na datamodel.MapAssembler) {
    42  							qp.MapEntry(na, selector.SelectorKey_Subset, qp.Map(1, func(na datamodel.MapAssembler) {
    43  								qp.MapEntry(na, selector.SelectorKey_From, qp.Int(from))
    44  								qp.MapEntry(na, selector.SelectorKey_To, qp.Int(to))
    45  							}))
    46  						}))
    47  					}))
    48  				}))
    49  			}))
    50  		})
    51  	}
    52  
    53  	for _, tc := range []struct {
    54  		from  int64
    55  		to    int64
    56  		exp   string
    57  		match bool
    58  	}{
    59  		{0, math.MaxInt64, expectedString, true},
    60  		{0, int64(len(expectedString)), expectedString, true},
    61  		{0, 0, "", true},
    62  		{0, 1, "f", true},
    63  		{0, 2, "fo", true},
    64  		{0, 3, "foo", true},
    65  		{0, 4, "foob", true},
    66  		{1, 4, "oob", true},
    67  		{2, 4, "ob", true},
    68  		{3, 4, "b", true},
    69  		{4, 4, "", true},
    70  		{4, math.MaxInt64, "arbaz!", true},
    71  		{4, int64(len(expectedString)), "arbaz!", true},
    72  		{4, int64(len(expectedString) - 1), "arbaz", true},
    73  		{0, int64(len(expectedString) - 1), expectedString[0 : len(expectedString)-1], true},
    74  		{0, int64(len(expectedString) - 2), expectedString[0 : len(expectedString)-2], true},
    75  		{0, -1, expectedString[0 : len(expectedString)-1], true},
    76  		{0, -2, expectedString[0 : len(expectedString)-2], true},
    77  		{-2, -1, "z", true},
    78  		{-1, math.MaxInt64, "!", true},
    79  		{-int64(len(expectedString)), math.MaxInt64, expectedString, true},
    80  		{math.MaxInt64 - 1, math.MaxInt64, "", false},
    81  		{int64(len(expectedString)), math.MaxInt64, "", false},
    82  		{-1, -2, "", false},                 // To < From, no match
    83  		{-1, -1, "", true},                  // To==From, match zero bytes
    84  		{-1000, -100, "", false},            // From undeflow, adjusted to 0, To underflow, not adjusted, To < From, no match
    85  		{-100, -1000, "", false},            // From undeflow, adjusted to 0, To underflow, adjusted to 0, To < From, no match
    86  		{-1000, 1000, expectedString, true}, // From undeflow, adjusted to 0, To overflow, adjusted to len, match all
    87  	} {
    88  		for _, variant := range nodes {
    89  			t.Run(fmt.Sprintf("%s[%d:%d]", variant.name, tc.from, tc.to), func(t *testing.T) {
    90  				selNode, err := mkRangeSelector(tc.from, tc.to)
    91  				qt.Assert(t, err, qt.IsNil)
    92  				ss, err := selector.ParseSelector(selNode)
    93  				qt.Assert(t, err, qt.IsNil)
    94  
    95  				// node that the selector will match, with our variant node embedded in the "bipbop" field
    96  				n, err := qp.BuildMap(basicnode.Prototype.Map, 1, func(na datamodel.MapAssembler) {
    97  					qp.MapEntry(na, "bipbop", qp.Node(variant.node))
    98  				})
    99  
   100  				var got datamodel.Node
   101  				qt.Assert(t, err, qt.IsNil)
   102  				err = traversal.WalkMatching(n, ss, func(prog traversal.Progress, n datamodel.Node) error {
   103  					qt.Assert(t, got, qt.IsNil)
   104  					got = n
   105  					return nil
   106  				})
   107  				qt.Assert(t, err, qt.IsNil)
   108  
   109  				if tc.match {
   110  					qt.Assert(t, got, qt.IsNotNil)
   111  					qt.Assert(t, got.Kind(), qt.Equals, variant.node.Kind())
   112  					var gotString string
   113  					switch got.Kind() {
   114  					case datamodel.Kind_String:
   115  						gotString, err = got.AsString()
   116  						qt.Assert(t, err, qt.IsNil)
   117  					case datamodel.Kind_Bytes:
   118  						byts, err := got.AsBytes()
   119  						qt.Assert(t, err, qt.IsNil)
   120  						gotString = string(byts)
   121  					}
   122  					qt.Assert(t, gotString, qt.DeepEquals, tc.exp)
   123  				} else {
   124  					qt.Assert(t, got, qt.IsNil)
   125  				}
   126  			})
   127  		}
   128  	}
   129  
   130  	// when both are positive, we can validate ranges up-front
   131  	t.Run("invalid range", func(t *testing.T) {
   132  		selNode, err := mkRangeSelector(1000, 100)
   133  		qt.Assert(t, err, qt.IsNil)
   134  		re, err := regexp.Compile("from.*less than or equal to.*to")
   135  		qt.Assert(t, err, qt.IsNil)
   136  		ss, err := selector.ParseSelector(selNode)
   137  		qt.Assert(t, ss, qt.IsNil)
   138  		qt.Assert(t, err, qt.ErrorMatches, re)
   139  	})
   140  }