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