1package toml
2
3import (
4	"fmt"
5	"io/ioutil"
6	"sort"
7	"strings"
8	"testing"
9	"time"
10)
11
12type queryTestNode struct {
13	value    interface{}
14	position Position
15}
16
17func valueString(root interface{}) string {
18	result := "" //fmt.Sprintf("%T:", root)
19	switch node := root.(type) {
20	case *tomlValue:
21		return valueString(node.value)
22	case *QueryResult:
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 *TomlTree:
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, toml, query string, ref []interface{}) {
82	tree, err := Load(toml)
83	if err != nil {
84		t.Errorf("Non-nil toml parse error: %v", err)
85		return
86	}
87	q, err := CompileQuery(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				}, 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), 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), 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), 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), Position{2, 1},
149			},
150			queryTestNode{
151				int64(2), Position{2, 1},
152			},
153			queryTestNode{
154				int64(3), Position{2, 1},
155			},
156			queryTestNode{
157				int64(4), Position{2, 1},
158			},
159			queryTestNode{
160				int64(5), 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), Position{2, 1},
172			},
173			queryTestNode{
174				int64(3), Position{2, 1},
175			},
176			queryTestNode{
177				int64(5), 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				}, Position{1, 1},
192			},
193			queryTestNode{
194				map[string]interface{}{
195					"a": int64(3),
196					"b": int64(4),
197				}, 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				}, Position{1, 1},
211			},
212			queryTestNode{
213				map[string]interface{}{
214					"a": int64(3),
215					"b": int64(4),
216				}, Position{4, 1},
217			},
218			queryTestNode{
219				map[string]interface{}{
220					"a": int64(5),
221					"b": int64(6),
222				}, 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				}, 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				}, Position{1, 1},
261			},
262			queryTestNode{
263				map[string]interface{}{
264					"a": int64(1),
265					"b": int64(2),
266				}, Position{1, 1},
267			},
268			queryTestNode{
269				int64(1), Position{2, 1},
270			},
271			queryTestNode{
272				int64(2), 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				}, Position{4, 1},
281			},
282			queryTestNode{
283				map[string]interface{}{
284					"a": int64(3),
285					"b": int64(4),
286				}, Position{4, 1},
287			},
288			queryTestNode{
289				int64(3), Position{5, 1},
290			},
291			queryTestNode{
292				int64(4), 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				}, Position{7, 1},
301			},
302			queryTestNode{
303				map[string]interface{}{
304					"a": int64(5),
305					"b": int64(6),
306				}, Position{7, 1},
307			},
308			queryTestNode{
309				int64(5), Position{8, 1},
310			},
311			queryTestNode{
312				int64(6), 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				}, Position{1, 1},
329			},
330			queryTestNode{
331				map[string]interface{}{
332					"a": int64(3),
333					"b": int64(4),
334				}, Position{4, 1},
335			},
336			queryTestNode{
337				map[string]interface{}{
338					"a": int64(1),
339					"b": int64(2),
340				}, Position{1, 1},
341			},
342			queryTestNode{
343				map[string]interface{}{
344					"a": int64(5),
345					"b": int64(6),
346				}, 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), Position{13, 1},
363			},
364			queryTestNode{
365				int64(8001), Position{13, 1},
366			},
367			queryTestNode{
368				int64(8002), Position{13, 1},
369			},
370			queryTestNode{
371				int64(5000), Position{14, 1},
372			},
373		})
374
375	assertQueryPositions(t, string(buff),
376		"$..[?(string)]",
377		[]interface{}{
378			queryTestNode{
379				"TOML Example", Position{3, 1},
380			},
381			queryTestNode{
382				"Tom Preston-Werner", Position{6, 1},
383			},
384			queryTestNode{
385				"GitHub", Position{7, 1},
386			},
387			queryTestNode{
388				"GitHub Cofounder & CEO\nLikes tater tots and beer.",
389				Position{8, 1},
390			},
391			queryTestNode{
392				"192.168.1.1", Position{12, 1},
393			},
394			queryTestNode{
395				"10.0.0.1", Position{21, 3},
396			},
397			queryTestNode{
398				"eqdc10", Position{22, 3},
399			},
400			queryTestNode{
401				"10.0.0.2", Position{25, 3},
402			},
403			queryTestNode{
404				"eqdc10", Position{26, 3},
405			},
406		})
407
408	assertQueryPositions(t, string(buff),
409		"$..[?(float)]",
410		[]interface{}{
411		// no float values in document
412		})
413
414	tv, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z")
415	assertQueryPositions(t, string(buff),
416		"$..[?(tree)]",
417		[]interface{}{
418			queryTestNode{
419				map[string]interface{}{
420					"name":         "Tom Preston-Werner",
421					"organization": "GitHub",
422					"bio":          "GitHub Cofounder & CEO\nLikes tater tots and beer.",
423					"dob":          tv,
424				}, Position{5, 1},
425			},
426			queryTestNode{
427				map[string]interface{}{
428					"server":         "192.168.1.1",
429					"ports":          []interface{}{int64(8001), int64(8001), int64(8002)},
430					"connection_max": int64(5000),
431					"enabled":        true,
432				}, Position{11, 1},
433			},
434			queryTestNode{
435				map[string]interface{}{
436					"alpha": map[string]interface{}{
437						"ip": "10.0.0.1",
438						"dc": "eqdc10",
439					},
440					"beta": map[string]interface{}{
441						"ip": "10.0.0.2",
442						"dc": "eqdc10",
443					},
444				}, Position{17, 1},
445			},
446			queryTestNode{
447				map[string]interface{}{
448					"ip": "10.0.0.1",
449					"dc": "eqdc10",
450				}, Position{20, 3},
451			},
452			queryTestNode{
453				map[string]interface{}{
454					"ip": "10.0.0.2",
455					"dc": "eqdc10",
456				}, Position{24, 3},
457			},
458			queryTestNode{
459				map[string]interface{}{
460					"data": []interface{}{
461						[]interface{}{"gamma", "delta"},
462						[]interface{}{int64(1), int64(2)},
463					},
464				}, Position{28, 1},
465			},
466		})
467
468	assertQueryPositions(t, string(buff),
469		"$..[?(time)]",
470		[]interface{}{
471			queryTestNode{
472				tv, Position{9, 1},
473			},
474		})
475
476	assertQueryPositions(t, string(buff),
477		"$..[?(bool)]",
478		[]interface{}{
479			queryTestNode{
480				true, Position{15, 1},
481			},
482		})
483}
484