k8s.io/client-go@v0.22.2/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  	]`)
   306  	var pointsData interface{}
   307  	err := json.Unmarshal(pointsJSON, &pointsData)
   308  	if err != nil {
   309  		t.Error(err)
   310  	}
   311  	pointsTests := []jsonpathTest{
   312  		{"exists filter", "{[?(@.z)].id}", pointsData, "i2 i5", false},
   313  		{"bracket key", "{[0]['id']}", pointsData, "i1", false},
   314  	}
   315  	testJSONPath(pointsTests, false, t)
   316  }
   317  
   318  // TestKubernetes tests some use cases from kubernetes
   319  func TestKubernetes(t *testing.T) {
   320  	var input = []byte(`{
   321  	  "kind": "List",
   322  	  "items":[
   323  		{
   324  		  "kind":"None",
   325  		  "metadata":{
   326  		    "name":"127.0.0.1",
   327  			"labels":{
   328  			  "kubernetes.io/hostname":"127.0.0.1"
   329  			}
   330  		  },
   331  		  "status":{
   332  			"capacity":{"cpu":"4"},
   333  			"ready": true,
   334  			"addresses":[{"type": "LegacyHostIP", "address":"127.0.0.1"}]
   335  		  }
   336  		},
   337  		{
   338  		  "kind":"None",
   339  		  "metadata":{
   340  			"name":"127.0.0.2",
   341  			"labels":{
   342  			  "kubernetes.io/hostname":"127.0.0.2"
   343  			}
   344  		  },
   345  		  "status":{
   346  			"capacity":{"cpu":"8"},
   347  			"ready": false,
   348  			"addresses":[
   349  			  {"type": "LegacyHostIP", "address":"127.0.0.2"},
   350  			  {"type": "another", "address":"127.0.0.3"}
   351  			]
   352  		  }
   353  		}
   354  	  ],
   355  	  "users":[
   356  	    {
   357  	      "name": "myself",
   358  	      "user": {}
   359  	    },
   360  	    {
   361  	      "name": "e2e",
   362  	      "user": {"username": "admin", "password": "secret"}
   363  	  	}
   364  	  ]
   365  	}`)
   366  	var nodesData interface{}
   367  	err := json.Unmarshal(input, &nodesData)
   368  	if err != nil {
   369  		t.Error(err)
   370  	}
   371  
   372  	nodesTests := []jsonpathTest{
   373  		{"range item", `{range .items[*]}{.metadata.name}, {end}{.kind}`, nodesData, "127.0.0.1, 127.0.0.2, List", false},
   374  		{"range item with quote", `{range .items[*]}{.metadata.name}{"\t"}{end}`, nodesData, "127.0.0.1\t127.0.0.2\t", false},
   375  		{"range addresss", `{.items[*].status.addresses[*].address}`, nodesData,
   376  			"127.0.0.1 127.0.0.2 127.0.0.3", false},
   377  		{"double range", `{range .items[*]}{range .status.addresses[*]}{.address}, {end}{end}`, nodesData,
   378  			"127.0.0.1, 127.0.0.2, 127.0.0.3, ", false},
   379  		{"item name", `{.items[*].metadata.name}`, nodesData, "127.0.0.1 127.0.0.2", false},
   380  		{"union nodes capacity", `{.items[*]['metadata.name', 'status.capacity']}`, nodesData,
   381  			`127.0.0.1 127.0.0.2 {"cpu":"4"} {"cpu":"8"}`, false},
   382  		{"range nodes capacity", `{range .items[*]}[{.metadata.name}, {.status.capacity}] {end}`, nodesData,
   383  			`[127.0.0.1, {"cpu":"4"}] [127.0.0.2, {"cpu":"8"}] `, false},
   384  		{"user password", `{.users[?(@.name=="e2e")].user.password}`, &nodesData, "secret", false},
   385  		{"hostname", `{.items[0].metadata.labels.kubernetes\.io/hostname}`, &nodesData, "127.0.0.1", false},
   386  		{"hostname filter", `{.items[?(@.metadata.labels.kubernetes\.io/hostname=="127.0.0.1")].kind}`, &nodesData, "None", false},
   387  		{"bool item", `{.items[?(@..ready==true)].metadata.name}`, &nodesData, "127.0.0.1", false},
   388  	}
   389  	testJSONPath(nodesTests, false, t)
   390  
   391  	randomPrintOrderTests := []jsonpathTest{
   392  		{"recursive name", "{..name}", nodesData, `127.0.0.1 127.0.0.2 myself e2e`, false},
   393  	}
   394  	testJSONPathSortOutput(randomPrintOrderTests, t)
   395  }
   396  
   397  func TestEmptyRange(t *testing.T) {
   398  	var input = []byte(`{"items":[]}`)
   399  	var emptyList interface{}
   400  	err := json.Unmarshal(input, &emptyList)
   401  	if err != nil {
   402  		t.Error(err)
   403  	}
   404  
   405  	tests := []jsonpathTest{
   406  		{"empty range", `{range .items[*]}{.metadata.name}{end}`, &emptyList, "", false},
   407  		{"empty nested range", `{range .items[*]}{.metadata.name}{":"}{range @.spec.containers[*]}{.name}{","}{end}{"+"}{end}`, &emptyList, "", false},
   408  	}
   409  	testJSONPath(tests, true, t)
   410  }
   411  
   412  func TestNestedRanges(t *testing.T) {
   413  	var input = []byte(`{
   414  		"items": [
   415  			{
   416  				"metadata": {
   417  					"name": "pod1"
   418  				},
   419  				"spec": {
   420  					"containers": [
   421  						{
   422  							"name": "foo",
   423  							"another": [
   424  								{ "name": "value1" },
   425  								{ "name": "value2" }
   426  							]
   427  						},
   428  						{
   429  							"name": "bar",
   430  							"another": [
   431  								{ "name": "value1" },
   432  								{ "name": "value2" }
   433  							]
   434  						}
   435  					]
   436                  }
   437  			},
   438  			{
   439  				"metadata": {
   440  					"name": "pod2"
   441  				},
   442  				"spec": {
   443  					"containers": [
   444  						{
   445  							"name": "baz",
   446  							"another": [
   447  								{ "name": "value1" },
   448  								{ "name": "value2" }
   449  							]
   450  						}
   451  					]
   452                  }
   453  			}
   454  		]
   455  	}`)
   456  	var data interface{}
   457  	err := json.Unmarshal(input, &data)
   458  	if err != nil {
   459  		t.Error(err)
   460  	}
   461  
   462  	testJSONPath(
   463  		[]jsonpathTest{
   464  			{
   465  				"nested range with a trailing newline",
   466  				`{range .items[*]}` +
   467  					`{.metadata.name}` +
   468  					`{":"}` +
   469  					`{range @.spec.containers[*]}` +
   470  					`{.name}` +
   471  					`{","}` +
   472  					`{end}` +
   473  					`{"+"}` +
   474  					`{end}`,
   475  				data,
   476  				"pod1:foo,bar,+pod2:baz,+",
   477  				false,
   478  			},
   479  		},
   480  		false,
   481  		t,
   482  	)
   483  
   484  	testJSONPath(
   485  		[]jsonpathTest{
   486  			{
   487  				"nested range with a trailing character within another nested range with a trailing newline",
   488  				`{range .items[*]}` +
   489  					`{.metadata.name}` +
   490  					`{"~"}` +
   491  					`{range @.spec.containers[*]}` +
   492  					`{.name}` +
   493  					`{":"}` +
   494  					`{range @.another[*]}` +
   495  					`{.name}` +
   496  					`{","}` +
   497  					`{end}` +
   498  					`{"+"}` +
   499  					`{end}` +
   500  					`{"#"}` +
   501  					`{end}`,
   502  				data,
   503  				"pod1~foo:value1,value2,+bar:value1,value2,+#pod2~baz:value1,value2,+#",
   504  				false,
   505  			},
   506  		},
   507  		false,
   508  		t,
   509  	)
   510  
   511  	testJSONPath(
   512  		[]jsonpathTest{
   513  			{
   514  				"two nested ranges at the same level with a trailing newline",
   515  				`{range .items[*]}` +
   516  					`{.metadata.name}` +
   517  					`{"\t"}` +
   518  					`{range @.spec.containers[*]}` +
   519  					`{.name}` +
   520  					`{" "}` +
   521  					`{end}` +
   522  					`{"\t"}` +
   523  					`{range @.spec.containers[*]}` +
   524  					`{.name}` +
   525  					`{" "}` +
   526  					`{end}` +
   527  					`{"\n"}` +
   528  					`{end}`,
   529  				data,
   530  				"pod1\tfoo bar \tfoo bar \npod2\tbaz \tbaz \n",
   531  				false,
   532  			},
   533  		},
   534  		false,
   535  		t,
   536  	)
   537  }
   538  
   539  func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) {
   540  	// for https://issues.k8s.io/45546
   541  	var input = []byte(`{
   542  		"kind": "List",
   543  		"items": [
   544  			{
   545  				"kind": "Pod",
   546  				"metadata": {
   547  					"name": "pod1",
   548  					"annotations": {
   549  						"color": "blue"
   550  					}
   551  				}
   552  			},
   553  			{
   554  				"kind": "Pod",
   555  				"metadata": {
   556  					"name": "pod2"
   557  				}
   558  			},
   559  			{
   560  				"kind": "Pod",
   561  				"metadata": {
   562  					"name": "pod3",
   563  					"annotations": {
   564  						"color": "green"
   565  					}
   566  				}
   567  			},
   568  			{
   569  				"kind": "Pod",
   570  				"metadata": {
   571  					"name": "pod4",
   572  					"annotations": {
   573  						"color": "blue"
   574  					}
   575  				}
   576  			}
   577  		]
   578  	}`)
   579  	var data interface{}
   580  	err := json.Unmarshal(input, &data)
   581  	if err != nil {
   582  		t.Fatal(err)
   583  	}
   584  
   585  	testJSONPath(
   586  		[]jsonpathTest{
   587  			{
   588  				"filter, should only match a subset, some items don't have annotations, tolerate missing items",
   589  				`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
   590  				data,
   591  				"pod1 pod4",
   592  				false, // expect no error
   593  			},
   594  		},
   595  		true, // allow missing keys
   596  		t,
   597  	)
   598  
   599  	testJSONPath(
   600  		[]jsonpathTest{
   601  			{
   602  				"filter, should only match a subset, some items don't have annotations, error on missing items",
   603  				`{.items[?(@.metadata.annotations.color=="blue")].metadata.name}`,
   604  				data,
   605  				"",
   606  				true, // expect an error
   607  			},
   608  		},
   609  		false, // don't allow missing keys
   610  		t,
   611  	)
   612  }
   613  
   614  func TestNegativeIndex(t *testing.T) {
   615  	var input = []byte(
   616  		`{
   617  			"apiVersion": "v1",
   618  			"kind": "Pod",
   619  			"spec": {
   620  				"containers": [
   621  					{
   622  						"image": "radial/busyboxplus:curl",
   623  						"name": "fake0"
   624  					},
   625  					{
   626  						"image": "radial/busyboxplus:curl",
   627  						"name": "fake1"
   628  					},
   629  					{
   630  						"image": "radial/busyboxplus:curl",
   631  						"name": "fake2"
   632  					},
   633  					{
   634  						"image": "radial/busyboxplus:curl",
   635  						"name": "fake3"
   636  					}]}}`)
   637  
   638  	var data interface{}
   639  	err := json.Unmarshal(input, &data)
   640  	if err != nil {
   641  		t.Fatal(err)
   642  	}
   643  
   644  	testJSONPath(
   645  		[]jsonpathTest{
   646  			{
   647  				"test containers[0], it equals containers[0]",
   648  				`{.spec.containers[0].name}`,
   649  				data,
   650  				"fake0",
   651  				false,
   652  			},
   653  			{
   654  				"test containers[0:0], it equals the empty set",
   655  				`{.spec.containers[0:0].name}`,
   656  				data,
   657  				"",
   658  				false,
   659  			},
   660  			{
   661  				"test containers[0:-1], it equals containers[0:3]",
   662  				`{.spec.containers[0:-1].name}`,
   663  				data,
   664  				"fake0 fake1 fake2",
   665  				false,
   666  			},
   667  			{
   668  				"test containers[-1:0], expect error",
   669  				`{.spec.containers[-1:0].name}`,
   670  				data,
   671  				"",
   672  				true,
   673  			},
   674  			{
   675  				"test containers[-1], it equals containers[3]",
   676  				`{.spec.containers[-1].name}`,
   677  				data,
   678  				"fake3",
   679  				false,
   680  			},
   681  			{
   682  				"test containers[-1:], it equals containers[3:]",
   683  				`{.spec.containers[-1:].name}`,
   684  				data,
   685  				"fake3",
   686  				false,
   687  			},
   688  			{
   689  				"test containers[-2], it equals containers[2]",
   690  				`{.spec.containers[-2].name}`,
   691  				data,
   692  				"fake2",
   693  				false,
   694  			},
   695  			{
   696  				"test containers[-2:], it equals containers[2:]",
   697  				`{.spec.containers[-2:].name}`,
   698  				data,
   699  				"fake2 fake3",
   700  				false,
   701  			},
   702  			{
   703  				"test containers[-3], it equals containers[1]",
   704  				`{.spec.containers[-3].name}`,
   705  				data,
   706  				"fake1",
   707  				false,
   708  			},
   709  			{
   710  				"test containers[-4], it equals containers[0]",
   711  				`{.spec.containers[-4].name}`,
   712  				data,
   713  				"fake0",
   714  				false,
   715  			},
   716  			{
   717  				"test containers[-4:], it equals containers[0:]",
   718  				`{.spec.containers[-4:].name}`,
   719  				data,
   720  				"fake0 fake1 fake2 fake3",
   721  				false,
   722  			},
   723  			{
   724  				"test containers[-5], expect a error cause it out of bounds",
   725  				`{.spec.containers[-5].name}`,
   726  				data,
   727  				"",
   728  				true, // expect error
   729  			},
   730  			{
   731  				"test containers[5:5], expect empty set",
   732  				`{.spec.containers[5:5].name}`,
   733  				data,
   734  				"",
   735  				false,
   736  			},
   737  			{
   738  				"test containers[-5:-5], expect empty set",
   739  				`{.spec.containers[-5:-5].name}`,
   740  				data,
   741  				"",
   742  				false,
   743  			},
   744  			{
   745  				"test containers[3:1], expect a error cause start index is greater than end index",
   746  				`{.spec.containers[3:1].name}`,
   747  				data,
   748  				"",
   749  				true,
   750  			},
   751  			{
   752  				"test containers[-1:-2], it equals containers[3:2], expect a error cause start index is greater than end index",
   753  				`{.spec.containers[-1:-2].name}`,
   754  				data,
   755  				"",
   756  				true,
   757  			},
   758  		},
   759  		false,
   760  		t,
   761  	)
   762  }
   763  
   764  func TestRunningPodsJSONPathOutput(t *testing.T) {
   765  	var input = []byte(`{
   766  		"kind": "List",
   767  		"items": [
   768  			{
   769  				"kind": "Pod",
   770  				"metadata": {
   771  					"name": "pod1"
   772  				},
   773  				"status": {
   774  						"phase": "Running"
   775  				}
   776  			},
   777  			{
   778  				"kind": "Pod",
   779  				"metadata": {
   780  					"name": "pod2"
   781  				},
   782  				"status": {
   783  						"phase": "Running"
   784  				}
   785  			},
   786  			{
   787  				"kind": "Pod",
   788  				"metadata": {
   789  					"name": "pod3"
   790  				},
   791  				"status": {
   792  						"phase": "Running"
   793  				}
   794  			},
   795             		{
   796  				"resourceVersion": "",
   797  				"selfLink": ""
   798  			}
   799  		]
   800  	}`)
   801  	var data interface{}
   802  	err := json.Unmarshal(input, &data)
   803  	if err != nil {
   804  		t.Fatal(err)
   805  	}
   806  
   807  	testJSONPath(
   808  		[]jsonpathTest{
   809  			{
   810  				"range over pods without selecting the last one",
   811  				`{range .items[?(.status.phase=="Running")]}{.metadata.name}{" is Running\n"}{end}`,
   812  				data,
   813  				"pod1 is Running\npod2 is Running\npod3 is Running\n",
   814  				false, // expect no error
   815  			},
   816  		},
   817  		true, // allow missing keys
   818  		t,
   819  	)
   820  }
   821  
   822  func TestStep(t *testing.T) {
   823  	var input = []byte(
   824  		`{
   825  			"apiVersion": "v1",
   826  			"kind": "Pod",
   827  			"spec": {
   828  				"containers": [
   829  					{
   830  						"image": "radial/busyboxplus:curl",
   831  						"name": "fake0"
   832  					},
   833  					{
   834  						"image": "radial/busyboxplus:curl",
   835  						"name": "fake1"
   836  					},
   837  					{
   838  						"image": "radial/busyboxplus:curl",
   839  						"name": "fake2"
   840  					},
   841  					{
   842  						"image": "radial/busyboxplus:curl",
   843  						"name": "fake3"
   844  					},
   845  					{
   846  						"image": "radial/busyboxplus:curl",
   847  						"name": "fake4"
   848  					},
   849  					{
   850  						"image": "radial/busyboxplus:curl",
   851  						"name": "fake5"
   852  					}]}}`)
   853  
   854  	var data interface{}
   855  	err := json.Unmarshal(input, &data)
   856  	if err != nil {
   857  		t.Fatal(err)
   858  	}
   859  
   860  	testJSONPath(
   861  		[]jsonpathTest{
   862  			{
   863  				"test containers[0:], it equals containers[0:6:1]",
   864  				`{.spec.containers[0:].name}`,
   865  				data,
   866  				"fake0 fake1 fake2 fake3 fake4 fake5",
   867  				false,
   868  			},
   869  			{
   870  				"test containers[0:6:], it equals containers[0:6:1]",
   871  				`{.spec.containers[0:6:].name}`,
   872  				data,
   873  				"fake0 fake1 fake2 fake3 fake4 fake5",
   874  				false,
   875  			},
   876  			{
   877  				"test containers[0:6:1]",
   878  				`{.spec.containers[0:6:1].name}`,
   879  				data,
   880  				"fake0 fake1 fake2 fake3 fake4 fake5",
   881  				false,
   882  			},
   883  			{
   884  				"test containers[0:6:0], it errors",
   885  				`{.spec.containers[0:6:0].name}`,
   886  				data,
   887  				"",
   888  				true,
   889  			},
   890  			{
   891  				"test containers[0:6:-1], it errors",
   892  				`{.spec.containers[0:6:-1].name}`,
   893  				data,
   894  				"",
   895  				true,
   896  			},
   897  			{
   898  				"test containers[1:4:2]",
   899  				`{.spec.containers[1:4:2].name}`,
   900  				data,
   901  				"fake1 fake3",
   902  				false,
   903  			},
   904  			{
   905  				"test containers[1:4:3]",
   906  				`{.spec.containers[1:4:3].name}`,
   907  				data,
   908  				"fake1",
   909  				false,
   910  			},
   911  			{
   912  				"test containers[1:4:4]",
   913  				`{.spec.containers[1:4:4].name}`,
   914  				data,
   915  				"fake1",
   916  				false,
   917  			},
   918  			{
   919  				"test containers[0:6:2]",
   920  				`{.spec.containers[0:6:2].name}`,
   921  				data,
   922  				"fake0 fake2 fake4",
   923  				false,
   924  			},
   925  			{
   926  				"test containers[0:6:3]",
   927  				`{.spec.containers[0:6:3].name}`,
   928  				data,
   929  				"fake0 fake3",
   930  				false,
   931  			},
   932  			{
   933  				"test containers[0:6:5]",
   934  				`{.spec.containers[0:6:5].name}`,
   935  				data,
   936  				"fake0 fake5",
   937  				false,
   938  			},
   939  			{
   940  				"test containers[0:6:6]",
   941  				`{.spec.containers[0:6:6].name}`,
   942  				data,
   943  				"fake0",
   944  				false,
   945  			},
   946  		},
   947  		false,
   948  		t,
   949  	)
   950  }