1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package jsonpath
18
19import (
20	"testing"
21)
22
23type parserTest struct {
24	name        string
25	text        string
26	nodes       []Node
27	shouldError bool
28}
29
30var parserTests = []parserTest{
31	{"plain", `hello jsonpath`, []Node{newText("hello jsonpath")}, false},
32	{"variable", `hello {.jsonpath}`,
33		[]Node{newText("hello "), newList(), newField("jsonpath")}, false},
34	{"arrayfiled", `hello {['jsonpath']}`,
35		[]Node{newText("hello "), newList(), newField("jsonpath")}, false},
36	{"quote", `{"{"}`, []Node{newList(), newText("{")}, false},
37	{"array", `{[1:3]}`, []Node{newList(),
38		newArray([3]ParamsEntry{{1, true, false}, {3, true, false}, {0, false, false}})}, false},
39	{"allarray", `{.book[*].author}`,
40		[]Node{newList(), newField("book"),
41			newArray([3]ParamsEntry{{0, false, false}, {0, false, false}, {0, false, false}}), newField("author")}, false},
42	{"wildcard", `{.bicycle.*}`,
43		[]Node{newList(), newField("bicycle"), newWildcard()}, false},
44	{"filter", `{[?(@.price<3)]}`,
45		[]Node{newList(), newFilter(newList(), newList(), "<"),
46			newList(), newField("price"), newList(), newInt(3)}, false},
47	{"recursive", `{..}`, []Node{newList(), newRecursive()}, false},
48	{"recurField", `{..price}`,
49		[]Node{newList(), newRecursive(), newField("price")}, false},
50	{"arraydict", `{['book.price']}`, []Node{newList(),
51		newField("book"), newField("price"),
52	}, false},
53	{"union", `{['bicycle.price', 3, 'book.price']}`, []Node{newList(), newUnion([]*ListNode{}),
54		newList(), newField("bicycle"), newField("price"),
55		newList(), newArray([3]ParamsEntry{{3, true, false}, {4, true, true}, {0, false, false}}),
56		newList(), newField("book"), newField("price"),
57	}, false},
58	{"range", `{range .items}{.name},{end}`, []Node{
59		newList(), newIdentifier("range"), newField("items"),
60		newList(), newField("name"), newText(","),
61		newList(), newIdentifier("end"),
62	}, false},
63	{"malformat input", `{\\\}`, []Node{}, true},
64	{"paired parentheses in quotes", `{[?(@.status.nodeInfo.osImage == "()")]}`,
65		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("()")}, false},
66	{"paired parentheses in double quotes and with double quotes escape", `{[?(@.status.nodeInfo.osImage == "(\"\")")]}`,
67		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("(\"\")")}, false},
68	{"unregular parentheses in double quotes", `{[?(@.test == "())(")]}`,
69		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("test"), newList(), newText("())(")}, false},
70	{"plain text in single quotes", `{[?(@.status.nodeInfo.osImage == 'Linux')]}`,
71		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("Linux")}, false},
72	{"test filter suffix", `{[?(@.status.nodeInfo.osImage == "{[()]}")]}`,
73		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("{[()]}")}, false},
74	{"double inside single", `{[?(@.status.nodeInfo.osImage == "''")]}`,
75		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("''")}, false},
76	{"single inside double", `{[?(@.status.nodeInfo.osImage == '""')]}`,
77		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\"\"")}, false},
78	{"single containing escaped single", `{[?(@.status.nodeInfo.osImage == '\\\'')]}`,
79		[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\\'")}, false},
80	{"negative index slice, equals a[len-5] to a[len-1]", `{[-5:]}`, []Node{newList(),
81		newArray([3]ParamsEntry{{-5, true, false}, {0, false, false}, {0, false, false}})}, false},
82	{"negative index slice, equals a[len-1]", `{[-1]}`, []Node{newList(),
83		newArray([3]ParamsEntry{{-1, true, false}, {0, true, true}, {0, false, false}})}, false},
84	{"negative index slice, equals a[1] to a[len-1]", `{[1:-1]}`, []Node{newList(),
85		newArray([3]ParamsEntry{{1, true, false}, {-1, true, false}, {0, false, false}})}, false},
86}
87
88func collectNode(nodes []Node, cur Node) []Node {
89	nodes = append(nodes, cur)
90	switch cur.Type() {
91	case NodeList:
92		for _, node := range cur.(*ListNode).Nodes {
93			nodes = collectNode(nodes, node)
94		}
95	case NodeFilter:
96		nodes = collectNode(nodes, cur.(*FilterNode).Left)
97		nodes = collectNode(nodes, cur.(*FilterNode).Right)
98	case NodeUnion:
99		for _, node := range cur.(*UnionNode).Nodes {
100			nodes = collectNode(nodes, node)
101		}
102	}
103	return nodes
104}
105
106func TestParser(t *testing.T) {
107	for _, test := range parserTests {
108		parser, err := Parse(test.name, test.text)
109		if test.shouldError {
110			if err == nil {
111				t.Errorf("unexpected non-error when parsing %s", test.name)
112			}
113			continue
114		}
115		if err != nil {
116			t.Errorf("parse %s error %v", test.name, err)
117		}
118		result := collectNode([]Node{}, parser.Root)[1:]
119		if len(result) != len(test.nodes) {
120			t.Errorf("in %s, expect to get %d nodes, got %d nodes", test.name, len(test.nodes), len(result))
121			t.Error(result)
122		}
123		for i, expect := range test.nodes {
124			if result[i].String() != expect.String() {
125				t.Errorf("in %s, %dth node, expect %v, got %v", test.name, i, expect, result[i])
126			}
127		}
128	}
129}
130
131type failParserTest struct {
132	name string
133	text string
134	err  string
135}
136
137func TestFailParser(t *testing.T) {
138	failParserTests := []failParserTest{
139		{"unclosed action", "{.hello", "unclosed action"},
140		{"unrecognized character", "{*}", "unrecognized character in action: U+002A '*'"},
141		{"invalid number", "{+12.3.0}", "cannot parse number +12.3.0"},
142		{"unterminated array", "{[1}", "unterminated array"},
143		{"unterminated filter", "{[?(.price]}", "unterminated filter"},
144	}
145	for _, test := range failParserTests {
146		_, err := Parse(test.name, test.text)
147		var out string
148		if err == nil {
149			out = "nil"
150		} else {
151			out = err.Error()
152		}
153		if out != test.err {
154			t.Errorf("in %s, expect to get error %v, got %v", test.name, test.err, out)
155		}
156	}
157}
158