1
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
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
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
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
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
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
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
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
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
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
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
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,
595 },
596 },
597 true,
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,
609 },
610 },
611 false,
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,
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,
816 },
817 },
818 true,
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 }
952
View as plain text