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 }