github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/services/oracle/jsonpath/jsonpath_test.go (about)

     1  package jsonpath
     2  
     3  import (
     4  	"bytes"
     5  	"math"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  
    10  	json "github.com/nspcc-dev/go-ordered-json"
    11  	"github.com/stretchr/testify/require"
    12  )
    13  
    14  type pathTestCase struct {
    15  	path   string
    16  	result string
    17  }
    18  
    19  func unmarshalGet(t *testing.T, js string, path string) ([]any, bool) {
    20  	var v any
    21  	buf := bytes.NewBuffer([]byte(js))
    22  	d := json.NewDecoder(buf)
    23  	d.UseOrderedObject()
    24  	require.NoError(t, d.Decode(&v))
    25  	return Get(path, v)
    26  }
    27  
    28  func (p *pathTestCase) testUnmarshalGet(t *testing.T, js string) {
    29  	res, ok := unmarshalGet(t, js, p.path)
    30  	require.True(t, ok)
    31  
    32  	data, err := json.Marshal(res)
    33  	require.NoError(t, err)
    34  	require.JSONEq(t, p.result, string(data))
    35  }
    36  
    37  func TestInvalidPaths(t *testing.T) {
    38  	bigNum := strconv.FormatInt(int64(math.MaxInt32)+1, 10)
    39  
    40  	// errCases contains invalid json path expressions.
    41  	// These are either invalid(&) or unexpected token in some positions
    42  	// or big number/invalid string.
    43  	errCases := []string{
    44  		".",
    45  		"$1",
    46  		"&",
    47  		"$&",
    48  		"$.&",
    49  		"$.[0]",
    50  		"$..",
    51  		"$..*",
    52  		"$..&",
    53  		"$..1",
    54  		"$[&]",
    55  		"$[**]",
    56  		"$[1&]",
    57  		"$[" + bigNum + "]",
    58  		"$[" + bigNum + ":]",
    59  		"$[:" + bigNum + "]",
    60  		"$[1," + bigNum + "]",
    61  		"$[" + bigNum + "[]]",
    62  		"$['a'&]",
    63  		"$['a'1]",
    64  		"$['a",
    65  		"$['\\u123']",
    66  		"$['s','\\u123']",
    67  		"$[[]]",
    68  		"$[1,'a']",
    69  		"$[1,1&",
    70  		"$[1,1[]]",
    71  		"$[1:&]",
    72  		"$[1:1[]]",
    73  		"$[1:[]]",
    74  		"$[1:[]]",
    75  		"$[",
    76  	}
    77  
    78  	for _, tc := range errCases {
    79  		t.Run(tc, func(t *testing.T) {
    80  			_, ok := unmarshalGet(t, "{}", tc)
    81  			require.False(t, ok)
    82  		})
    83  	}
    84  }
    85  
    86  func TestDescendByIdent(t *testing.T) {
    87  	js := `{
    88  		"store": {
    89  			"name": "big",
    90  			"sub": [
    91  				{ "name": "sub1" },
    92  				{ "name": "sub2" }
    93  			],
    94  			"partner": { "name": "ppp" }
    95  		},
    96  		"another": { "name": "small" }
    97  	}`
    98  
    99  	testCases := []pathTestCase{
   100  		{"$.store.name", `["big"]`},
   101  		{"$['store']['name']", `["big"]`},
   102  		{"$[*].name", `["big","small"]`},
   103  		{"$.*.name", `["big","small"]`},
   104  		{"$..store.name", `["big"]`},
   105  		{"$.store..name", `["big","ppp","sub1","sub2"]`},
   106  		{"$..sub.name", `[]`},
   107  		{"$..sub..name", `["sub1","sub2"]`},
   108  	}
   109  	for _, tc := range testCases {
   110  		t.Run(tc.path, func(t *testing.T) {
   111  			tc.testUnmarshalGet(t, js)
   112  		})
   113  	}
   114  
   115  	t.Run("big depth", func(t *testing.T) {
   116  		js := `{"a":{"b":{"c":{"d":{"e":{"f":{"g":1}}}}}}}`
   117  		t.Run("single field", func(t *testing.T) {
   118  			t.Run("max", func(t *testing.T) {
   119  				p := pathTestCase{"$.a.b.c.d.e.f", `[{"g":1}]`}
   120  				p.testUnmarshalGet(t, js)
   121  			})
   122  
   123  			_, ok := unmarshalGet(t, js, "$.a.b.c.d.e.f.g")
   124  			require.False(t, ok)
   125  		})
   126  		t.Run("wildcard", func(t *testing.T) {
   127  			t.Run("max", func(t *testing.T) {
   128  				p := pathTestCase{"$.*.*.*.*.*.*", `[{"g":1}]`}
   129  				p.testUnmarshalGet(t, js)
   130  			})
   131  
   132  			_, ok := unmarshalGet(t, js, "$.*.*.*.*.*.*.*")
   133  			require.False(t, ok)
   134  		})
   135  	})
   136  }
   137  
   138  func TestDescendByIndex(t *testing.T) {
   139  	js := `["a","b","c","d"]`
   140  
   141  	testCases := []pathTestCase{
   142  		{"$[0]", `["a"]`},
   143  		{"$[3]", `["d"]`},
   144  		{"$[1:2]", `["b"]`},
   145  		{"$[1:-1]", `["b","c"]`},
   146  		{"$[-3:-1]", `["b","c"]`},
   147  		{"$[-3:3]", `["b","c"]`},
   148  		{"$[:3]", `["a","b","c"]`},
   149  		{"$[:100]", `["a","b","c","d"]`},
   150  		{"$[1:]", `["b","c","d"]`},
   151  		{"$[2:1]", `[]`},
   152  	}
   153  
   154  	for _, tc := range testCases {
   155  		t.Run(tc.path, func(t *testing.T) {
   156  			tc.testUnmarshalGet(t, js)
   157  		})
   158  	}
   159  
   160  	t.Run("big depth", func(t *testing.T) {
   161  		js := `[[[[[[[1]]]]]]]`
   162  		t.Run("single index", func(t *testing.T) {
   163  			t.Run("max", func(t *testing.T) {
   164  				p := pathTestCase{"$[0][0][0][0][0][0]", "[[1]]"}
   165  				p.testUnmarshalGet(t, js)
   166  			})
   167  
   168  			_, ok := unmarshalGet(t, js, "$[0][0][0][0][0][0][0]")
   169  			require.False(t, ok)
   170  		})
   171  		t.Run("slice", func(t *testing.T) {
   172  			t.Run("max", func(t *testing.T) {
   173  				p := pathTestCase{"$[0:][0:][0:][0:][0:][0:]", "[[1]]"}
   174  				p.testUnmarshalGet(t, js)
   175  			})
   176  
   177  			_, ok := unmarshalGet(t, js, "$[0:][0:][0:][0:][0:][0:][0:]")
   178  			require.False(t, ok)
   179  		})
   180  	})
   181  
   182  	t.Run("$[:][1], skip wrong types", func(t *testing.T) {
   183  		js := `[[1,2],{"1":"4"},[5,6]]`
   184  		p := pathTestCase{"$[:][1]", "[2,6]"}
   185  		p.testUnmarshalGet(t, js)
   186  	})
   187  
   188  	t.Run("$[*].*, flatten", func(t *testing.T) {
   189  		js := `[[1,2],{"1":"4"},[5,6]]`
   190  		p := pathTestCase{"$[*].*", "[1,2,\"4\",5,6]"}
   191  		p.testUnmarshalGet(t, js)
   192  	})
   193  
   194  	t.Run("$[*].[1:], skip wrong types", func(t *testing.T) {
   195  		js := `[[1,2],3,{"1":"4"},[5,6]]`
   196  		p := pathTestCase{"$[*][1:]", "[2,6]"}
   197  		p.testUnmarshalGet(t, js)
   198  	})
   199  }
   200  
   201  func TestUnion(t *testing.T) {
   202  	js := `["a",{"x":1,"y":2,"z":3},"c","d"]`
   203  
   204  	testCases := []pathTestCase{
   205  		{"$[0,2]", `["a","c"]`},
   206  		{"$[1]['x','z']", `[1,3]`},
   207  	}
   208  
   209  	for _, tc := range testCases {
   210  		t.Run(tc.path, func(t *testing.T) {
   211  			tc.testUnmarshalGet(t, js)
   212  		})
   213  	}
   214  
   215  	t.Run("big amount of intermediate objects", func(t *testing.T) {
   216  		// We want to fail as early as possible, this test covers all possible
   217  		// places where an overflow could first occur. The idea is that first steps
   218  		// construct intermediate array of 1000 < 1024, and the last step multiplies
   219  		// this amount by 2.
   220  		construct := func(width int, index string) string {
   221  			return "[" + strings.Repeat(index+",", width-1) + index + "]"
   222  		}
   223  
   224  		t.Run("index, array", func(t *testing.T) {
   225  			jp := "$" + strings.Repeat(construct(10, "0"), 4)
   226  			_, ok := unmarshalGet(t, "[[[[{}]]]]", jp)
   227  			require.False(t, ok)
   228  		})
   229  
   230  		t.Run("asterisk, array", func(t *testing.T) {
   231  			jp := "$" + strings.Repeat(construct(10, `0`), 3) + ".*"
   232  			_, ok := unmarshalGet(t, `[[[[{},{}]]]]`, jp)
   233  			require.False(t, ok)
   234  		})
   235  
   236  		t.Run("range", func(t *testing.T) {
   237  			jp := "$" + strings.Repeat(construct(10, `0`), 3) + "[0:2]"
   238  			_, ok := unmarshalGet(t, `[[[[{},{}]]]]`, jp)
   239  			require.False(t, ok)
   240  		})
   241  
   242  		t.Run("recursive descent", func(t *testing.T) {
   243  			jp := "$" + strings.Repeat(construct(10, `0`), 3) + "..a"
   244  			_, ok := unmarshalGet(t, `[[[{"a":{"a":{}}}]]]`, jp)
   245  			require.False(t, ok)
   246  		})
   247  
   248  		t.Run("string union", func(t *testing.T) {
   249  			jp := "$" + strings.Repeat(construct(10, `0`), 3) + "['x','y']"
   250  			_, ok := unmarshalGet(t, `[[[{"x":{},"y":{}}]]]`, jp)
   251  			require.False(t, ok)
   252  		})
   253  
   254  		t.Run("index, map", func(t *testing.T) {
   255  			jp := "$" + strings.Repeat(construct(10, `"a"`), 4)
   256  			_, ok := unmarshalGet(t, `{"a":{"a":{"a":{"a":{}}}}}`, jp)
   257  			require.False(t, ok)
   258  		})
   259  
   260  		t.Run("asterisk, map", func(t *testing.T) {
   261  			jp := "$" + strings.Repeat(construct(10, `'a'`), 3) + ".*"
   262  			_, ok := unmarshalGet(t, `{"a":{"a":{"a":{"x":{},"y":{}}}}}`, jp)
   263  			require.False(t, ok)
   264  		})
   265  	})
   266  }
   267  
   268  // These tests are taken directly from C# code.
   269  func TestCSharpCompat(t *testing.T) {
   270  	js := `{
   271      "store": {
   272          "book": [
   273              {
   274                  "category": "reference",
   275                  "author": "Nigel Rees",
   276                  "title": "Sayings of the Century",
   277                  "price": 8.95
   278              },
   279              {
   280                  "category": "fiction",
   281                  "author": "Evelyn Waugh",
   282                  "title": "Sword of Honour",
   283                  "price": 12.99
   284              },
   285              {
   286                  "category": "fiction",
   287                  "author": "Herman Melville",
   288                  "title": "Moby Dick",
   289                  "isbn": "0-553-21311-3",
   290                  "price": 8.99
   291              },
   292              {
   293                  "category": "fiction",
   294                  "author": "J. R. R. Tolkien",
   295                  "title": "The Lord of the Rings",
   296                  "isbn": "0-395-19395-8",
   297                  "price": null
   298              }
   299          ],
   300          "bicycle": {
   301                  "color": "red",
   302              "price": 19.95
   303          }
   304          },
   305      "expensive": 10,
   306      "data": null
   307  }`
   308  
   309  	testCases := []pathTestCase{
   310  		{"$.store.book[*].author", `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`},
   311  		{"$..author", `["Nigel Rees","Evelyn Waugh","Herman Melville","J. R. R. Tolkien"]`},
   312  		{"$.store.*", `[[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}],{"color":"red","price":19.95}]`},
   313  		{"$.store..price", `[19.95,8.95,12.99,8.99,null]`},
   314  		{"$..book[2]", `[{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}]`},
   315  		{"$..book[-2]", `[{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99}]`},
   316  		{"$..book[0,1]", `[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}]`},
   317  		{"$..book[:2]", `[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}]`},
   318  		{"$..book[1:2]", `[{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99}]`},
   319  		{"$..book[-2:]", `[{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}]`},
   320  		{"$..book[2:]", `[{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}]`},
   321  		{"", `[{"store":{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}],"bicycle":{"color":"red","price":19.95}},"expensive":10,"data":null}]`},
   322  		{"$.*", `[{"book":[{"category":"reference","author":"Nigel Rees","title":"Sayings of the Century","price":8.95},{"category":"fiction","author":"Evelyn Waugh","title":"Sword of Honour","price":12.99},{"category":"fiction","author":"Herman Melville","title":"Moby Dick","isbn":"0-553-21311-3","price":8.99},{"category":"fiction","author":"J. R. R. Tolkien","title":"The Lord of the Rings","isbn":"0-395-19395-8","price":null}],"bicycle":{"color":"red","price":19.95}},10,null]`},
   323  		{"$..invalidfield", `[]`},
   324  	}
   325  
   326  	for _, tc := range testCases {
   327  		t.Run(tc.path, func(t *testing.T) {
   328  			tc.testUnmarshalGet(t, js)
   329  		})
   330  	}
   331  
   332  	t.Run("bad cases", func(t *testing.T) {
   333  		_, ok := unmarshalGet(t, js, `$..book[*].author"`)
   334  		require.False(t, ok)
   335  	})
   336  }