k8s.io/client-go@v0.31.1/util/jsonpath/jsonpath_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package jsonpath 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "reflect" 24 "sort" 25 "strings" 26 "testing" 27 ) 28 29 type jsonpathTest struct { 30 name string 31 template string 32 input interface{} 33 expect string 34 expectError bool 35 } 36 37 func testJSONPath(tests []jsonpathTest, allowMissingKeys bool, t *testing.T) { 38 for _, test := range tests { 39 j := New(test.name) 40 j.AllowMissingKeys(allowMissingKeys) 41 err := j.Parse(test.template) 42 if err != nil { 43 if !test.expectError { 44 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 45 } 46 continue 47 } 48 buf := new(bytes.Buffer) 49 err = j.Execute(buf, test.input) 50 if test.expectError { 51 if err == nil { 52 t.Errorf(`in %s, expected execute error, got %q`, test.name, buf) 53 } 54 continue 55 } else if err != nil { 56 t.Errorf("in %s, execute error %v", test.name, err) 57 } 58 out := buf.String() 59 if out != test.expect { 60 t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out) 61 } 62 } 63 } 64 65 // testJSONPathSortOutput test cases related to map, the results may print in random order 66 func testJSONPathSortOutput(tests []jsonpathTest, t *testing.T) { 67 for _, test := range tests { 68 j := New(test.name) 69 err := j.Parse(test.template) 70 if err != nil { 71 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 72 } 73 buf := new(bytes.Buffer) 74 err = j.Execute(buf, test.input) 75 if err != nil { 76 t.Errorf("in %s, execute error %v", test.name, err) 77 } 78 out := buf.String() 79 //since map is visited in random order, we need to sort the results. 80 sortedOut := strings.Fields(out) 81 sort.Strings(sortedOut) 82 sortedExpect := strings.Fields(test.expect) 83 sort.Strings(sortedExpect) 84 if !reflect.DeepEqual(sortedOut, sortedExpect) { 85 t.Errorf(`in %s, expect to get "%s", got "%s"`, test.name, test.expect, out) 86 } 87 } 88 } 89 90 func testFailJSONPath(tests []jsonpathTest, t *testing.T) { 91 for _, test := range tests { 92 j := New(test.name) 93 err := j.Parse(test.template) 94 if err != nil { 95 t.Errorf("in %s, parse %s error %v", test.name, test.template, err) 96 } 97 buf := new(bytes.Buffer) 98 err = j.Execute(buf, test.input) 99 var out string 100 if err == nil { 101 out = "nil" 102 } else { 103 out = err.Error() 104 } 105 if out != test.expect { 106 t.Errorf("in %s, expect to get error %q, got %q", test.name, test.expect, out) 107 } 108 } 109 } 110 111 func TestTypesInput(t *testing.T) { 112 types := map[string]interface{}{ 113 "bools": []bool{true, false, true, false}, 114 "integers": []int{1, 2, 3, 4}, 115 "floats": []float64{1.0, 2.2, 3.3, 4.0}, 116 "strings": []string{"one", "two", "three", "four"}, 117 "interfaces": []interface{}{true, "one", 1, 1.1}, 118 "maps": []map[string]interface{}{ 119 {"name": "one", "value": 1}, 120 {"name": "two", "value": 2.02}, 121 {"name": "three", "value": 3.03}, 122 {"name": "four", "value": 4.04}, 123 }, 124 "structs": []struct { 125 Name string `json:"name"` 126 Value interface{} `json:"value"` 127 Type string `json:"type"` 128 }{ 129 {Name: "one", Value: 1, Type: "integer"}, 130 {Name: "two", Value: 2.002, Type: "float"}, 131 {Name: "three", Value: 3, Type: "integer"}, 132 {Name: "four", Value: 4.004, Type: "float"}, 133 }, 134 } 135 136 sliceTests := []jsonpathTest{ 137 // boolean slice tests 138 {"boolSlice", `{ .bools }`, types, `[true,false,true,false]`, false}, 139 {"boolSliceIndex", `{ .bools[0] }`, types, `true`, false}, 140 {"boolSliceIndex", `{ .bools[-1] }`, types, `false`, false}, 141 {"boolSubSlice", `{ .bools[0:2] }`, types, `true false`, false}, 142 {"boolSubSliceFirst2", `{ .bools[:2] }`, types, `true false`, false}, 143 {"boolSubSliceStep2", `{ .bools[:4:2] }`, types, `true true`, false}, 144 // integer slice tests 145 {"integerSlice", `{ .integers }`, types, `[1,2,3,4]`, false}, 146 {"integerSliceIndex", `{ .integers[0] }`, types, `1`, false}, 147 {"integerSliceIndexReverse", `{ .integers[-2] }`, types, `3`, false}, 148 {"integerSubSliceFirst2", `{ .integers[0:2] }`, types, `1 2`, false}, 149 {"integerSubSliceFirst2Alt", `{ .integers[:2] }`, types, `1 2`, false}, 150 {"integerSubSliceStep2", `{ .integers[:4:2] }`, types, `1 3`, false}, 151 // float slice tests 152 {"floatSlice", `{ .floats }`, types, `[1,2.2,3.3,4]`, false}, 153 {"floatSliceIndex", `{ .floats[0] }`, types, `1`, false}, 154 {"floatSliceIndexReverse", `{ .floats[-2] }`, types, `3.3`, false}, 155 {"floatSubSliceFirst2", `{ .floats[0:2] }`, types, `1 2.2`, false}, 156 {"floatSubSliceFirst2Alt", `{ .floats[:2] }`, types, `1 2.2`, false}, 157 {"floatSubSliceStep2", `{ .floats[:4:2] }`, types, `1 3.3`, false}, 158 // strings slice tests 159 {"stringSlice", `{ .strings }`, types, `["one","two","three","four"]`, false}, 160 {"stringSliceIndex", `{ .strings[0] }`, types, `one`, false}, 161 {"stringSliceIndexReverse", `{ .strings[-2] }`, types, `three`, false}, 162 {"stringSubSliceFirst2", `{ .strings[0:2] }`, types, `one two`, false}, 163 {"stringSubSliceFirst2Alt", `{ .strings[:2] }`, types, `one two`, false}, 164 {"stringSubSliceStep2", `{ .strings[:4:2] }`, types, `one three`, false}, 165 // interfaces slice tests 166 {"interfaceSlice", `{ .interfaces }`, types, `[true,"one",1,1.1]`, false}, 167 {"interfaceSliceIndex", `{ .interfaces[0] }`, types, `true`, false}, 168 {"interfaceSliceIndexReverse", `{ .interfaces[-2] }`, types, `1`, false}, 169 {"interfaceSubSliceFirst2", `{ .interfaces[0:2] }`, types, `true one`, false}, 170 {"interfaceSubSliceFirst2Alt", `{ .interfaces[:2] }`, types, `true one`, false}, 171 {"interfaceSubSliceStep2", `{ .interfaces[:4:2] }`, types, `true 1`, false}, 172 // maps slice tests 173 {"mapSlice", `{ .maps }`, types, 174 `[{"name":"one","value":1},{"name":"two","value":2.02},{"name":"three","value":3.03},{"name":"four","value":4.04}]`, false}, 175 {"mapSliceIndex", `{ .maps[0] }`, types, `{"name":"one","value":1}`, false}, 176 {"mapSliceIndexReverse", `{ .maps[-2] }`, types, `{"name":"three","value":3.03}`, false}, 177 {"mapSubSliceFirst2", `{ .maps[0:2] }`, types, `{"name":"one","value":1} {"name":"two","value":2.02}`, false}, 178 {"mapSubSliceFirst2Alt", `{ .maps[:2] }`, types, `{"name":"one","value":1} {"name":"two","value":2.02}`, false}, 179 {"mapSubSliceStepOdd", `{ .maps[::2] }`, types, `{"name":"one","value":1} {"name":"three","value":3.03}`, false}, 180 {"mapSubSliceStepEven", `{ .maps[1::2] }`, types, `{"name":"two","value":2.02} {"name":"four","value":4.04}`, false}, 181 // structs slice tests 182 {"structSlice", `{ .structs }`, types, 183 `[{"name":"one","value":1,"type":"integer"},{"name":"two","value":2.002,"type":"float"},{"name":"three","value":3,"type":"integer"},{"name":"four","value":4.004,"type":"float"}]`, false}, 184 {"structSliceIndex", `{ .structs[0] }`, types, `{"name":"one","value":1,"type":"integer"}`, false}, 185 {"structSliceIndexReverse", `{ .structs[-2] }`, types, `{"name":"three","value":3,"type":"integer"}`, false}, 186 {"structSubSliceFirst2", `{ .structs[0:2] }`, types, 187 `{"name":"one","value":1,"type":"integer"} {"name":"two","value":2.002,"type":"float"}`, false}, 188 {"structSubSliceFirst2Alt", `{ .structs[:2] }`, types, 189 `{"name":"one","value":1,"type":"integer"} {"name":"two","value":2.002,"type":"float"}`, false}, 190 {"structSubSliceStepOdd", `{ .structs[::2] }`, types, 191 `{"name":"one","value":1,"type":"integer"} {"name":"three","value":3,"type":"integer"}`, false}, 192 {"structSubSliceStepEven", `{ .structs[1::2] }`, types, 193 `{"name":"two","value":2.002,"type":"float"} {"name":"four","value":4.004,"type":"float"}`, false}, 194 } 195 196 testJSONPath(sliceTests, false, t) 197 } 198 199 type book struct { 200 Category string 201 Author string 202 Title string 203 Price float32 204 } 205 206 func (b book) String() string { 207 return fmt.Sprintf("{Category: %s, Author: %s, Title: %s, Price: %v}", b.Category, b.Author, b.Title, b.Price) 208 } 209 210 type bicycle struct { 211 Color string 212 Price float32 213 IsNew bool 214 } 215 216 type empName string 217 type job string 218 type store struct { 219 Book []book 220 Bicycle []bicycle 221 Name string 222 Labels map[string]int 223 Employees map[empName]job 224 } 225 226 func TestStructInput(t *testing.T) { 227 228 storeData := store{ 229 Name: "jsonpath", 230 Book: []book{ 231 {"reference", "Nigel Rees", "Sayings of the Centurey", 8.95}, 232 {"fiction", "Evelyn Waugh", "Sword of Honour", 12.99}, 233 {"fiction", "Herman Melville", "Moby Dick", 8.99}, 234 }, 235 Bicycle: []bicycle{ 236 {"red", 19.95, true}, 237 {"green", 20.01, false}, 238 }, 239 Labels: map[string]int{ 240 "engieer": 10, 241 "web/html": 15, 242 "k8s-app": 20, 243 }, 244 Employees: map[empName]job{ 245 "jason": "manager", 246 "dan": "clerk", 247 }, 248 } 249 250 storeTests := []jsonpathTest{ 251 {"plain", "hello jsonpath", nil, "hello jsonpath", false}, 252 {"recursive", "{..}", []int{1, 2, 3}, "[1,2,3]", false}, 253 {"filter", "{[?(@<5)]}", []int{2, 6, 3, 7}, "2 3", false}, 254 {"quote", `{"{"}`, nil, "{", false}, 255 {"union", "{[1,3,4]}", []int{0, 1, 2, 3, 4}, "1 3 4", false}, 256 {"array", "{[0:2]}", []string{"Monday", "Tudesday"}, "Monday Tudesday", false}, 257 {"variable", "hello {.Name}", storeData, "hello jsonpath", false}, 258 {"dict/", "{$.Labels.web/html}", storeData, "15", false}, 259 {"dict/", "{$.Employees.jason}", storeData, "manager", false}, 260 {"dict/", "{$.Employees.dan}", storeData, "clerk", false}, 261 {"dict-", "{.Labels.k8s-app}", storeData, "20", false}, 262 {"nest", "{.Bicycle[*].Color}", storeData, "red green", false}, 263 {"allarray", "{.Book[*].Author}", storeData, "Nigel Rees Evelyn Waugh Herman Melville", false}, 264 {"allfields", `{range .Bicycle[*]}{ "{" }{ @.* }{ "} " }{end}`, storeData, "{red 19.95 true} {green 20.01 false} ", false}, 265 {"recurfields", "{..Price}", storeData, "8.95 12.99 8.99 19.95 20.01", false}, 266 {"recurdotfields", "{...Price}", storeData, "8.95 12.99 8.99 19.95 20.01", false}, 267 {"superrecurfields", "{............................................................Price}", storeData, "", true}, 268 {"allstructsSlice", "{.Bicycle}", storeData, 269 `[{"Color":"red","Price":19.95,"IsNew":true},{"Color":"green","Price":20.01,"IsNew":false}]`, false}, 270 {"allstructs", `{range .Bicycle[*]}{ @ }{ " " }{end}`, storeData, 271 `{"Color":"red","Price":19.95,"IsNew":true} {"Color":"green","Price":20.01,"IsNew":false} `, false}, 272 {"lastarray", "{.Book[-1:]}", storeData, 273 `{"Category":"fiction","Author":"Herman Melville","Title":"Moby Dick","Price":8.99}`, false}, 274 {"recurarray", "{..Book[2]}", storeData, 275 `{"Category":"fiction","Author":"Herman Melville","Title":"Moby Dick","Price":8.99}`, false}, 276 {"bool", "{.Bicycle[?(@.IsNew==true)]}", storeData, `{"Color":"red","Price":19.95,"IsNew":true}`, false}, 277 } 278 279 testJSONPath(storeTests, false, t) 280 281 missingKeyTests := []jsonpathTest{ 282 {"nonexistent field", "{.hello}", storeData, "", false}, 283 {"nonexistent field 2", "before-{.hello}after", storeData, "before-after", false}, 284 } 285 testJSONPath(missingKeyTests, true, t) 286 287 failStoreTests := []jsonpathTest{ 288 {"invalid identifier", "{hello}", storeData, "unrecognized identifier hello", false}, 289 {"nonexistent field", "{.hello}", storeData, "hello is not found", false}, 290 {"invalid array", "{.Labels[0]}", storeData, "map[string]int is not array or slice", false}, 291 {"invalid filter operator", "{.Book[?(@.Price<>10)]}", storeData, "unrecognized filter operator <>", false}, 292 {"redundant end", "{range .Labels.*}{@}{end}{end}", storeData, "not in range, nothing to end", false}, 293 } 294 testFailJSONPath(failStoreTests, t) 295 } 296 297 func TestJSONInput(t *testing.T) { 298 var pointsJSON = []byte(`[ 299 {"id": "i1", "x":4, "y":-5}, 300 {"id": "i2", "x":-2, "y":-5, "z":1}, 301 {"id": "i3", "x": 8, "y": 3 }, 302 {"id": "i4", "x": -6, "y": -1 }, 303 {"id": "i5", "x": 0, "y": 2, "z": 1 }, 304 {"id": "i6", "x": 1, "y": 4 }, 305 {"id": "i7", "x": null, "y": 4 } 306 ]`) 307 var pointsData interface{} 308 err := json.Unmarshal(pointsJSON, &pointsData) 309 if err != nil { 310 t.Error(err) 311 } 312 pointsTests := []jsonpathTest{ 313 {"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5", false}, 314 {"bracket key", "{[0]['id']}", pointsData, "i1", false}, 315 {"nil value", "{[-1]['x']}", pointsData, "null", false}, 316 } 317 testJSONPath(pointsTests, false, t) 318 } 319 320 // TestKubernetes tests some use cases from kubernetes 321 func TestKubernetes(t *testing.T) { 322 var input = []byte(`{ 323 "kind": "List", 324 "items":[ 325 { 326 "kind":"None", 327 "metadata":{ 328 "name":"127.0.0.1", 329 "labels":{ 330 "kubernetes.io/hostname":"127.0.0.1" 331 } 332 }, 333 "status":{ 334 "capacity":{"cpu":"4"}, 335 "ready": true, 336 "addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}] 337 } 338 }, 339 { 340 "kind":"None", 341 "metadata":{ 342 "name":"127.0.0.2", 343 "labels":{ 344 "kubernetes.io/hostname":"127.0.0.2" 345 } 346 }, 347 "status":{ 348 "capacity":{"cpu":"8"}, 349 "ready": false, 350 "addresses":[ 351 {"type": "LegacyHostIP", "address":"127.0.0.2"}, 352 {"type": "another", "address":"127.0.0.3"} 353 ] 354 } 355 } 356 ], 357 "users":[ 358 { 359 "name": "myself", 360 "user": {} 361 }, 362 { 363 "name": "e2e", 364 "user": {"username": "admin", "password": "secret"} 365 } 366 ] 367 }`) 368 var nodesData interface{} 369 err := json.Unmarshal(input, &nodesData) 370 if err != nil { 371 t.Error(err) 372 } 373 374 nodesTests := []jsonpathTest{ 375 {"range item", `{range .items[*]}{.metadata.name}, {end}{.kind}`, nodesData, "127.0.0.1, 127.0.0.2, List", false}, 376 {"range item with quote", `{range .items[*]}{.metadata.name}{"\t"}{end}`, nodesData, "127.0.0.1\t127.0.0.2\t", false}, 377 {"range addresss", `{.items[*].status.addresses[*].address}`, nodesData, 378 "127.0.0.1 127.0.0.2 127.0.0.3", false}, 379 {"double range", `{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}`, nodesData, 380 "127.0.0.1, 127.0.0.2, 127.0.0.3, ", false}, 381 {"item name", `{.items[*].metadata.name}`, nodesData, "127.0.0.1 127.0.0.2", false}, 382 {"union nodes capacity", `{.items[*]['metadata.name', 'status.capacity']}`, nodesData, 383 `127.0.0.1 127.0.0.2 {"cpu":"4"} {"cpu":"8"}`, false}, 384 {"range nodes capacity", `{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}`, nodesData, 385 `[127.0.0.1, {"cpu":"4"}] [127.0.0.2, {"cpu":"8"}] `, false}, 386 {"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret", false}, 387 {"hostname", `{.items[0].metadata.labels.kubernetes\.io/hostname}`, &nodesData, "127.0.0.1", false}, 388 {"hostname filter", `{.items[?(@.metadata.labels.kubernetes\.io/hostname=="127.0.0.1")].kind}`, &nodesData, "None", false}, 389 {"bool item", `{.items[?(@..ready==true)].metadata.name}`, &nodesData, "127.0.0.1", false}, 390 } 391 testJSONPath(nodesTests, false, t) 392 393 randomPrintOrderTests := []jsonpathTest{ 394 {"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`, false}, 395 } 396 testJSONPathSortOutput(randomPrintOrderTests, t) 397 } 398 399 func TestEmptyRange(t *testing.T) { 400 var input = []byte(`{"items":[]}`) 401 var emptyList interface{} 402 err := json.Unmarshal(input, &emptyList) 403 if err != nil { 404 t.Error(err) 405 } 406 407 tests := []jsonpathTest{ 408 {"empty range", `{range .items[*]}{.metadata.name}{end}`, &emptyList, "", false}, 409 {"empty nested range", `{range .items[*]}{.metadata.name}{":"}{range @.spec.containers[*]}{.name}{","}{end}{"+"}{end}`, &emptyList, "", false}, 410 } 411 testJSONPath(tests, true, t) 412 } 413 414 func TestNestedRanges(t *testing.T) { 415 var input = []byte(`{ 416 "items": [ 417 { 418 "metadata": { 419 "name": "pod1" 420 }, 421 "spec": { 422 "containers": [ 423 { 424 "name": "foo", 425 "another": [ 426 { "name": "value1" }, 427 { "name": "value2" } 428 ] 429 }, 430 { 431 "name": "bar", 432 "another": [ 433 { "name": "value1" }, 434 { "name": "value2" } 435 ] 436 } 437 ] 438 } 439 }, 440 { 441 "metadata": { 442 "name": "pod2" 443 }, 444 "spec": { 445 "containers": [ 446 { 447 "name": "baz", 448 "another": [ 449 { "name": "value1" }, 450 { "name": "value2" } 451 ] 452 } 453 ] 454 } 455 } 456 ] 457 }`) 458 var data interface{} 459 err := json.Unmarshal(input, &data) 460 if err != nil { 461 t.Error(err) 462 } 463 464 testJSONPath( 465 []jsonpathTest{ 466 { 467 "nested range with a trailing newline", 468 `{range .items[*]}` + 469 `{.metadata.name}` + 470 `{":"}` + 471 `{range @.spec.containers[*]}` + 472 `{.name}` + 473 `{","}` + 474 `{end}` + 475 `{"+"}` + 476 `{end}`, 477 data, 478 "pod1:foo,bar,+pod2:baz,+", 479 false, 480 }, 481 }, 482 false, 483 t, 484 ) 485 486 testJSONPath( 487 []jsonpathTest{ 488 { 489 "nested range with a trailing character within another nested range with a trailing newline", 490 `{range .items[*]}` + 491 `{.metadata.name}` + 492 `{"~"}` + 493 `{range @.spec.containers[*]}` + 494 `{.name}` + 495 `{":"}` + 496 `{range @.another[*]}` + 497 `{.name}` + 498 `{","}` + 499 `{end}` + 500 `{"+"}` + 501 `{end}` + 502 `{"#"}` + 503 `{end}`, 504 data, 505 "pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#", 506 false, 507 }, 508 }, 509 false, 510 t, 511 ) 512 513 testJSONPath( 514 []jsonpathTest{ 515 { 516 "two nested ranges at the same level with a trailing newline", 517 `{range .items[*]}` + 518 `{.metadata.name}` + 519 `{"\t"}` + 520 `{range @.spec.containers[*]}` + 521 `{.name}` + 522 `{" "}` + 523 `{end}` + 524 `{"\t"}` + 525 `{range @.spec.containers[*]}` + 526 `{.name}` + 527 `{" "}` + 528 `{end}` + 529 `{"\n"}` + 530 `{end}`, 531 data, 532 "pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n", 533 false, 534 }, 535 }, 536 false, 537 t, 538 ) 539 } 540 541 func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) { 542 // for https://issues.k8s.io/45546 543 var input = []byte(`{ 544 "kind": "List", 545 "items": [ 546 { 547 "kind": "Pod", 548 "metadata": { 549 "name": "pod1", 550 "annotations": { 551 "color": "blue" 552 } 553 } 554 }, 555 { 556 "kind": "Pod", 557 "metadata": { 558 "name": "pod2" 559 } 560 }, 561 { 562 "kind": "Pod", 563 "metadata": { 564 "name": "pod3", 565 "annotations": { 566 "color": "green" 567 } 568 } 569 }, 570 { 571 "kind": "Pod", 572 "metadata": { 573 "name": "pod4", 574 "annotations": { 575 "color": "blue" 576 } 577 } 578 } 579 ] 580 }`) 581 var data interface{} 582 err := json.Unmarshal(input, &data) 583 if err != nil { 584 t.Fatal(err) 585 } 586 587 testJSONPath( 588 []jsonpathTest{ 589 { 590 "filter, should only match a subset, some items don't have annotations, tolerate missing items", 591 `{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`, 592 data, 593 "pod1 pod4", 594 false, // expect no error 595 }, 596 }, 597 true, // allow missing keys 598 t, 599 ) 600 601 testJSONPath( 602 []jsonpathTest{ 603 { 604 "filter, should only match a subset, some items don't have annotations, error on missing items", 605 `{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`, 606 data, 607 "", 608 true, // expect an error 609 }, 610 }, 611 false, // don't allow missing keys 612 t, 613 ) 614 } 615 616 func TestNegativeIndex(t *testing.T) { 617 var input = []byte( 618 `{ 619 "apiVersion": "v1", 620 "kind": "Pod", 621 "spec": { 622 "containers": [ 623 { 624 "image": "radial/busyboxplus:curl", 625 "name": "fake0" 626 }, 627 { 628 "image": "radial/busyboxplus:curl", 629 "name": "fake1" 630 }, 631 { 632 "image": "radial/busyboxplus:curl", 633 "name": "fake2" 634 }, 635 { 636 "image": "radial/busyboxplus:curl", 637 "name": "fake3" 638 }]}}`) 639 640 var data interface{} 641 err := json.Unmarshal(input, &data) 642 if err != nil { 643 t.Fatal(err) 644 } 645 646 testJSONPath( 647 []jsonpathTest{ 648 { 649 "test containers[0], it equals containers[0]", 650 `{.spec.containers[0].name}`, 651 data, 652 "fake0", 653 false, 654 }, 655 { 656 "test containers[0:0], it equals the empty set", 657 `{.spec.containers[0:0].name}`, 658 data, 659 "", 660 false, 661 }, 662 { 663 "test containers[0:-1], it equals containers[0:3]", 664 `{.spec.containers[0:-1].name}`, 665 data, 666 "fake0 fake1 fake2", 667 false, 668 }, 669 { 670 "test containers[-1:0], expect error", 671 `{.spec.containers[-1:0].name}`, 672 data, 673 "", 674 true, 675 }, 676 { 677 "test containers[-1], it equals containers[3]", 678 `{.spec.containers[-1].name}`, 679 data, 680 "fake3", 681 false, 682 }, 683 { 684 "test containers[-1:], it equals containers[3:]", 685 `{.spec.containers[-1:].name}`, 686 data, 687 "fake3", 688 false, 689 }, 690 { 691 "test containers[-2], it equals containers[2]", 692 `{.spec.containers[-2].name}`, 693 data, 694 "fake2", 695 false, 696 }, 697 { 698 "test containers[-2:], it equals containers[2:]", 699 `{.spec.containers[-2:].name}`, 700 data, 701 "fake2 fake3", 702 false, 703 }, 704 { 705 "test containers[-3], it equals containers[1]", 706 `{.spec.containers[-3].name}`, 707 data, 708 "fake1", 709 false, 710 }, 711 { 712 "test containers[-4], it equals containers[0]", 713 `{.spec.containers[-4].name}`, 714 data, 715 "fake0", 716 false, 717 }, 718 { 719 "test containers[-4:], it equals containers[0:]", 720 `{.spec.containers[-4:].name}`, 721 data, 722 "fake0 fake1 fake2 fake3", 723 false, 724 }, 725 { 726 "test containers[-5], expect a error cause it out of bounds", 727 `{.spec.containers[-5].name}`, 728 data, 729 "", 730 true, // expect error 731 }, 732 { 733 "test containers[5:5], expect empty set", 734 `{.spec.containers[5:5].name}`, 735 data, 736 "", 737 false, 738 }, 739 { 740 "test containers[-5:-5], expect empty set", 741 `{.spec.containers[-5:-5].name}`, 742 data, 743 "", 744 false, 745 }, 746 { 747 "test containers[3:1], expect a error cause start index is greater than end index", 748 `{.spec.containers[3:1].name}`, 749 data, 750 "", 751 true, 752 }, 753 { 754 "test containers[-1:-2], it equals containers[3:2], expect a error cause start index is greater than end index", 755 `{.spec.containers[-1:-2].name}`, 756 data, 757 "", 758 true, 759 }, 760 }, 761 false, 762 t, 763 ) 764 } 765 766 func TestRunningPodsJSONPathOutput(t *testing.T) { 767 var input = []byte(`{ 768 "kind": "List", 769 "items": [ 770 { 771 "kind": "Pod", 772 "metadata": { 773 "name": "pod1" 774 }, 775 "status": { 776 "phase": "Running" 777 } 778 }, 779 { 780 "kind": "Pod", 781 "metadata": { 782 "name": "pod2" 783 }, 784 "status": { 785 "phase": "Running" 786 } 787 }, 788 { 789 "kind": "Pod", 790 "metadata": { 791 "name": "pod3" 792 }, 793 "status": { 794 "phase": "Running" 795 } 796 }, 797 { 798 "resourceVersion": "" 799 } 800 ] 801 }`) 802 var data interface{} 803 err := json.Unmarshal(input, &data) 804 if err != nil { 805 t.Fatal(err) 806 } 807 808 testJSONPath( 809 []jsonpathTest{ 810 { 811 "range over pods without selecting the last one", 812 `{range .items[?(.status.phase=="Running")]}{.metadata.name}{" is Running\n"}{end}`, 813 data, 814 "pod1 is Running\npod2 is Running\npod3 is Running\n", 815 false, // expect no error 816 }, 817 }, 818 true, // allow missing keys 819 t, 820 ) 821 } 822 823 func TestStep(t *testing.T) { 824 var input = []byte( 825 `{ 826 "apiVersion": "v1", 827 "kind": "Pod", 828 "spec": { 829 "containers": [ 830 { 831 "image": "radial/busyboxplus:curl", 832 "name": "fake0" 833 }, 834 { 835 "image": "radial/busyboxplus:curl", 836 "name": "fake1" 837 }, 838 { 839 "image": "radial/busyboxplus:curl", 840 "name": "fake2" 841 }, 842 { 843 "image": "radial/busyboxplus:curl", 844 "name": "fake3" 845 }, 846 { 847 "image": "radial/busyboxplus:curl", 848 "name": "fake4" 849 }, 850 { 851 "image": "radial/busyboxplus:curl", 852 "name": "fake5" 853 }]}}`) 854 855 var data interface{} 856 err := json.Unmarshal(input, &data) 857 if err != nil { 858 t.Fatal(err) 859 } 860 861 testJSONPath( 862 []jsonpathTest{ 863 { 864 "test containers[0:], it equals containers[0:6:1]", 865 `{.spec.containers[0:].name}`, 866 data, 867 "fake0 fake1 fake2 fake3 fake4 fake5", 868 false, 869 }, 870 { 871 "test containers[0:6:], it equals containers[0:6:1]", 872 `{.spec.containers[0:6:].name}`, 873 data, 874 "fake0 fake1 fake2 fake3 fake4 fake5", 875 false, 876 }, 877 { 878 "test containers[0:6:1]", 879 `{.spec.containers[0:6:1].name}`, 880 data, 881 "fake0 fake1 fake2 fake3 fake4 fake5", 882 false, 883 }, 884 { 885 "test containers[0:6:0], it errors", 886 `{.spec.containers[0:6:0].name}`, 887 data, 888 "", 889 true, 890 }, 891 { 892 "test containers[0:6:-1], it errors", 893 `{.spec.containers[0:6:-1].name}`, 894 data, 895 "", 896 true, 897 }, 898 { 899 "test containers[1:4:2]", 900 `{.spec.containers[1:4:2].name}`, 901 data, 902 "fake1 fake3", 903 false, 904 }, 905 { 906 "test containers[1:4:3]", 907 `{.spec.containers[1:4:3].name}`, 908 data, 909 "fake1", 910 false, 911 }, 912 { 913 "test containers[1:4:4]", 914 `{.spec.containers[1:4:4].name}`, 915 data, 916 "fake1", 917 false, 918 }, 919 { 920 "test containers[0:6:2]", 921 `{.spec.containers[0:6:2].name}`, 922 data, 923 "fake0 fake2 fake4", 924 false, 925 }, 926 { 927 "test containers[0:6:3]", 928 `{.spec.containers[0:6:3].name}`, 929 data, 930 "fake0 fake3", 931 false, 932 }, 933 { 934 "test containers[0:6:5]", 935 `{.spec.containers[0:6:5].name}`, 936 data, 937 "fake0 fake5", 938 false, 939 }, 940 { 941 "test containers[0:6:6]", 942 `{.spec.containers[0:6:6].name}`, 943 data, 944 "fake0", 945 false, 946 }, 947 }, 948 false, 949 t, 950 ) 951 }