1package query
2
3import (
4	"fmt"
5	"io/ioutil"
6	"sort"
7	"strings"
8	"testing"
9	"time"
10	"github.com/pelletier/go-toml"
11)
12
13type queryTestNode struct {
14	value    interface{}
15	position toml.Position
16}
17
18func valueString(root interface{}) string {
19	result := "" //fmt.Sprintf("%T:", root)
20	switch node := root.(type) {
21	case *Result:
22		items := []string{}
23		for i, v := range node.Values() {
24			items = append(items, fmt.Sprintf("%s:%s",
25				node.Positions()[i].String(), valueString(v)))
26		}
27		sort.Strings(items)
28		result = "[" + strings.Join(items, ", ") + "]"
29	case queryTestNode:
30		result = fmt.Sprintf("%s:%s",
31			node.position.String(), valueString(node.value))
32	case []interface{}:
33		items := []string{}
34		for _, v := range node {
35			items = append(items, valueString(v))
36		}
37		sort.Strings(items)
38		result = "[" + strings.Join(items, ", ") + "]"
39	case *toml.Tree:
40		// workaround for unreliable map key ordering
41		items := []string{}
42		for _, k := range node.Keys() {
43			v := node.GetPath([]string{k})
44			items = append(items, k+":"+valueString(v))
45		}
46		sort.Strings(items)
47		result = "{" + strings.Join(items, ", ") + "}"
48	case map[string]interface{}:
49		// workaround for unreliable map key ordering
50		items := []string{}
51		for k, v := range node {
52			items = append(items, k+":"+valueString(v))
53		}
54		sort.Strings(items)
55		result = "{" + strings.Join(items, ", ") + "}"
56	case int64:
57		result += fmt.Sprintf("%d", node)
58	case string:
59		result += "'" + node + "'"
60	case float64:
61		result += fmt.Sprintf("%f", node)
62	case bool:
63		result += fmt.Sprintf("%t", node)
64	case time.Time:
65		result += fmt.Sprintf("'%v'", node)
66	}
67	return result
68}
69
70func assertValue(t *testing.T, result, ref interface{}) {
71	pathStr := valueString(result)
72	refStr := valueString(ref)
73	if pathStr != refStr {
74		t.Errorf("values do not match")
75		t.Log("test:", pathStr)
76		t.Log("ref: ", refStr)
77	}
78}
79
80func assertQueryPositions(t *testing.T, tomlDoc string, query string, ref []interface{}) {
81	tree, err := toml.Load(tomlDoc)
82	if err != nil {
83		t.Errorf("Non-nil toml parse error: %v", err)
84		return
85	}
86	q, err := Compile(query)
87	if err != nil {
88		t.Error(err)
89		return
90	}
91	results := q.Execute(tree)
92	assertValue(t, results, ref)
93}
94
95func TestQueryRoot(t *testing.T) {
96	assertQueryPositions(t,
97		"a = 42",
98		"$",
99		[]interface{}{
100			queryTestNode{
101				map[string]interface{}{
102					"a": int64(42),
103				}, toml.Position{1, 1},
104			},
105		})
106}
107
108func TestQueryKey(t *testing.T) {
109	assertQueryPositions(t,
110		"[foo]\na = 42",
111		"$.foo.a",
112		[]interface{}{
113			queryTestNode{
114				int64(42), toml.Position{2, 1},
115			},
116		})
117}
118
119func TestQueryKeyString(t *testing.T) {
120	assertQueryPositions(t,
121		"[foo]\na = 42",
122		"$.foo['a']",
123		[]interface{}{
124			queryTestNode{
125				int64(42), toml.Position{2, 1},
126			},
127		})
128}
129
130func TestQueryIndex(t *testing.T) {
131	assertQueryPositions(t,
132		"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
133		"$.foo.a[5]",
134		[]interface{}{
135			queryTestNode{
136				int64(6), toml.Position{2, 1},
137			},
138		})
139}
140
141func TestQuerySliceRange(t *testing.T) {
142	assertQueryPositions(t,
143		"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
144		"$.foo.a[0:5]",
145		[]interface{}{
146			queryTestNode{
147				int64(1), toml.Position{2, 1},
148			},
149			queryTestNode{
150				int64(2), toml.Position{2, 1},
151			},
152			queryTestNode{
153				int64(3), toml.Position{2, 1},
154			},
155			queryTestNode{
156				int64(4), toml.Position{2, 1},
157			},
158			queryTestNode{
159				int64(5), toml.Position{2, 1},
160			},
161		})
162}
163
164func TestQuerySliceStep(t *testing.T) {
165	assertQueryPositions(t,
166		"[foo]\na = [1,2,3,4,5,6,7,8,9,0]",
167		"$.foo.a[0:5:2]",
168		[]interface{}{
169			queryTestNode{
170				int64(1), toml.Position{2, 1},
171			},
172			queryTestNode{
173				int64(3), toml.Position{2, 1},
174			},
175			queryTestNode{
176				int64(5), toml.Position{2, 1},
177			},
178		})
179}
180
181func TestQueryAny(t *testing.T) {
182	assertQueryPositions(t,
183		"[foo.bar]\na=1\nb=2\n[foo.baz]\na=3\nb=4",
184		"$.foo.*",
185		[]interface{}{
186			queryTestNode{
187				map[string]interface{}{
188					"a": int64(1),
189					"b": int64(2),
190				}, toml.Position{1, 1},
191			},
192			queryTestNode{
193				map[string]interface{}{
194					"a": int64(3),
195					"b": int64(4),
196				}, toml.Position{4, 1},
197			},
198		})
199}
200func TestQueryUnionSimple(t *testing.T) {
201	assertQueryPositions(t,
202		"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
203		"$.*[bar,foo]",
204		[]interface{}{
205			queryTestNode{
206				map[string]interface{}{
207					"a": int64(1),
208					"b": int64(2),
209				}, toml.Position{1, 1},
210			},
211			queryTestNode{
212				map[string]interface{}{
213					"a": int64(3),
214					"b": int64(4),
215				}, toml.Position{4, 1},
216			},
217			queryTestNode{
218				map[string]interface{}{
219					"a": int64(5),
220					"b": int64(6),
221				}, toml.Position{7, 1},
222			},
223		})
224}
225
226func TestQueryRecursionAll(t *testing.T) {
227	assertQueryPositions(t,
228		"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
229		"$..*",
230		[]interface{}{
231			queryTestNode{
232				map[string]interface{}{
233					"foo": map[string]interface{}{
234						"bar": map[string]interface{}{
235							"a": int64(1),
236							"b": int64(2),
237						},
238					},
239					"baz": map[string]interface{}{
240						"foo": map[string]interface{}{
241							"a": int64(3),
242							"b": int64(4),
243						},
244					},
245					"gorf": map[string]interface{}{
246						"foo": map[string]interface{}{
247							"a": int64(5),
248							"b": int64(6),
249						},
250					},
251				}, toml.Position{1, 1},
252			},
253			queryTestNode{
254				map[string]interface{}{
255					"bar": map[string]interface{}{
256						"a": int64(1),
257						"b": int64(2),
258					},
259				}, toml.Position{1, 1},
260			},
261			queryTestNode{
262				map[string]interface{}{
263					"a": int64(1),
264					"b": int64(2),
265				}, toml.Position{1, 1},
266			},
267			queryTestNode{
268				int64(1), toml.Position{2, 1},
269			},
270			queryTestNode{
271				int64(2), toml.Position{3, 1},
272			},
273			queryTestNode{
274				map[string]interface{}{
275					"foo": map[string]interface{}{
276						"a": int64(3),
277						"b": int64(4),
278					},
279				}, toml.Position{4, 1},
280			},
281			queryTestNode{
282				map[string]interface{}{
283					"a": int64(3),
284					"b": int64(4),
285				}, toml.Position{4, 1},
286			},
287			queryTestNode{
288				int64(3), toml.Position{5, 1},
289			},
290			queryTestNode{
291				int64(4), toml.Position{6, 1},
292			},
293			queryTestNode{
294				map[string]interface{}{
295					"foo": map[string]interface{}{
296						"a": int64(5),
297						"b": int64(6),
298					},
299				}, toml.Position{7, 1},
300			},
301			queryTestNode{
302				map[string]interface{}{
303					"a": int64(5),
304					"b": int64(6),
305				}, toml.Position{7, 1},
306			},
307			queryTestNode{
308				int64(5), toml.Position{8, 1},
309			},
310			queryTestNode{
311				int64(6), toml.Position{9, 1},
312			},
313		})
314}
315
316func TestQueryRecursionUnionSimple(t *testing.T) {
317	assertQueryPositions(t,
318		"[foo.bar]\na=1\nb=2\n[baz.foo]\na=3\nb=4\n[gorf.foo]\na=5\nb=6",
319		"$..['foo','bar']",
320		[]interface{}{
321			queryTestNode{
322				map[string]interface{}{
323					"bar": map[string]interface{}{
324						"a": int64(1),
325						"b": int64(2),
326					},
327				}, toml.Position{1, 1},
328			},
329			queryTestNode{
330				map[string]interface{}{
331					"a": int64(3),
332					"b": int64(4),
333				}, toml.Position{4, 1},
334			},
335			queryTestNode{
336				map[string]interface{}{
337					"a": int64(1),
338					"b": int64(2),
339				}, toml.Position{1, 1},
340			},
341			queryTestNode{
342				map[string]interface{}{
343					"a": int64(5),
344					"b": int64(6),
345				}, toml.Position{7, 1},
346			},
347		})
348}
349
350func TestQueryFilterFn(t *testing.T) {
351	buff, err := ioutil.ReadFile("../example.toml")
352	if err != nil {
353		t.Error(err)
354		return
355	}
356
357	assertQueryPositions(t, string(buff),
358		"$..[?(int)]",
359		[]interface{}{
360			queryTestNode{
361				int64(8001), toml.Position{13, 1},
362			},
363			queryTestNode{
364				int64(8001), toml.Position{13, 1},
365			},
366			queryTestNode{
367				int64(8002), toml.Position{13, 1},
368			},
369			queryTestNode{
370				int64(5000), toml.Position{14, 1},
371			},
372		})
373
374	assertQueryPositions(t, string(buff),
375		"$..[?(string)]",
376		[]interface{}{
377			queryTestNode{
378				"TOML Example", toml.Position{3, 1},
379			},
380			queryTestNode{
381				"Tom Preston-Werner", toml.Position{6, 1},
382			},
383			queryTestNode{
384				"GitHub", toml.Position{7, 1},
385			},
386			queryTestNode{
387				"GitHub Cofounder & CEO\nLikes tater tots and beer.",
388				toml.Position{8, 1},
389			},
390			queryTestNode{
391				"192.168.1.1", toml.Position{12, 1},
392			},
393			queryTestNode{
394				"10.0.0.1", toml.Position{21, 3},
395			},
396			queryTestNode{
397				"eqdc10", toml.Position{22, 3},
398			},
399			queryTestNode{
400				"10.0.0.2", toml.Position{25, 3},
401			},
402			queryTestNode{
403				"eqdc10", toml.Position{26, 3},
404			},
405		})
406
407	assertQueryPositions(t, string(buff),
408		"$..[?(float)]",
409		[]interface{}{
410		// no float values in document
411		})
412
413	tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
414	assertQueryPositions(t, string(buff),
415		"$..[?(tree)]",
416		[]interface{}{
417			queryTestNode{
418				map[string]interface{}{
419					"name":         "Tom Preston-Werner",
420					"organization": "GitHub",
421					"bio":          "GitHub Cofounder & CEO\nLikes tater tots and beer.",
422					"dob":          tv,
423				}, toml.Position{5, 1},
424			},
425			queryTestNode{
426				map[string]interface{}{
427					"server":         "192.168.1.1",
428					"ports":          []interface{}{int64(8001), int64(8001), int64(8002)},
429					"connection_max": int64(5000),
430					"enabled":        true,
431				}, toml.Position{11, 1},
432			},
433			queryTestNode{
434				map[string]interface{}{
435					"alpha": map[string]interface{}{
436						"ip": "10.0.0.1",
437						"dc": "eqdc10",
438					},
439					"beta": map[string]interface{}{
440						"ip": "10.0.0.2",
441						"dc": "eqdc10",
442					},
443				}, toml.Position{17, 1},
444			},
445			queryTestNode{
446				map[string]interface{}{
447					"ip": "10.0.0.1",
448					"dc": "eqdc10",
449				}, toml.Position{20, 3},
450			},
451			queryTestNode{
452				map[string]interface{}{
453					"ip": "10.0.0.2",
454					"dc": "eqdc10",
455				}, toml.Position{24, 3},
456			},
457			queryTestNode{
458				map[string]interface{}{
459					"data": []interface{}{
460						[]interface{}{"gamma", "delta"},
461						[]interface{}{int64(1), int64(2)},
462					},
463				}, toml.Position{28, 1},
464			},
465		})
466
467	assertQueryPositions(t, string(buff),
468		"$..[?(time)]",
469		[]interface{}{
470			queryTestNode{
471				tv, toml.Position{9, 1},
472			},
473		})
474
475	assertQueryPositions(t, string(buff),
476		"$..[?(bool)]",
477		[]interface{}{
478			queryTestNode{
479				true, toml.Position{15, 1},
480			},
481		})
482}
483