1// Copyright 2011 Google Inc. All Rights Reserved.
2// Use of this source code is governed by the Apache 2.0
3// license that can be found in the LICENSE file.
4
5package datastore
6
7import (
8	"reflect"
9	"sort"
10	"testing"
11	"time"
12
13	"google.golang.org/appengine"
14)
15
16func TestValidPropertyName(t *testing.T) {
17	testCases := []struct {
18		name string
19		want bool
20	}{
21		// Invalid names.
22		{"", false},
23		{"'", false},
24		{".", false},
25		{"..", false},
26		{".foo", false},
27		{"0", false},
28		{"00", false},
29		{"X.X.4.X.X", false},
30		{"\n", false},
31		{"\x00", false},
32		{"abc\xffz", false},
33		{"foo.", false},
34		{"foo..", false},
35		{"foo..bar", false},
36		{"☃", false},
37		{`"`, false},
38		// Valid names.
39		{"AB", true},
40		{"Abc", true},
41		{"X.X.X.X.X", true},
42		{"_", true},
43		{"_0", true},
44		{"a", true},
45		{"a_B", true},
46		{"f00", true},
47		{"f0o", true},
48		{"fo0", true},
49		{"foo", true},
50		{"foo.bar", true},
51		{"foo.bar.baz", true},
52		{"世界", true},
53	}
54	for _, tc := range testCases {
55		got := validPropertyName(tc.name)
56		if got != tc.want {
57			t.Errorf("%q: got %v, want %v", tc.name, got, tc.want)
58		}
59	}
60}
61
62func TestStructCodec(t *testing.T) {
63	type oStruct struct {
64		O int
65	}
66	type pStruct struct {
67		P int
68		Q int
69	}
70	type rStruct struct {
71		R int
72		S pStruct
73		T oStruct
74		oStruct
75	}
76	type uStruct struct {
77		U int
78		v int
79	}
80	type vStruct struct {
81		V string `datastore:",noindex"`
82	}
83	oStructCodec := &structCodec{
84		fields: map[string]fieldCodec{
85			"O": {path: []int{0}},
86		},
87		complete: true,
88	}
89	pStructCodec := &structCodec{
90		fields: map[string]fieldCodec{
91			"P": {path: []int{0}},
92			"Q": {path: []int{1}},
93		},
94		complete: true,
95	}
96	rStructCodec := &structCodec{
97		fields: map[string]fieldCodec{
98			"R": {path: []int{0}},
99			"S": {path: []int{1}, structCodec: pStructCodec},
100			"T": {path: []int{2}, structCodec: oStructCodec},
101			"O": {path: []int{3, 0}},
102		},
103		complete: true,
104	}
105	uStructCodec := &structCodec{
106		fields: map[string]fieldCodec{
107			"U": {path: []int{0}},
108		},
109		complete: true,
110	}
111	vStructCodec := &structCodec{
112		fields: map[string]fieldCodec{
113			"V": {path: []int{0}, noIndex: true},
114		},
115		complete: true,
116	}
117
118	testCases := []struct {
119		desc        string
120		structValue interface{}
121		want        *structCodec
122	}{
123		{
124			"oStruct",
125			oStruct{},
126			oStructCodec,
127		},
128		{
129			"pStruct",
130			pStruct{},
131			pStructCodec,
132		},
133		{
134			"rStruct",
135			rStruct{},
136			rStructCodec,
137		},
138		{
139			"uStruct",
140			uStruct{},
141			uStructCodec,
142		},
143		{
144			"non-basic fields",
145			struct {
146				B appengine.BlobKey
147				K *Key
148				T time.Time
149			}{},
150			&structCodec{
151				fields: map[string]fieldCodec{
152					"B": {path: []int{0}},
153					"K": {path: []int{1}},
154					"T": {path: []int{2}},
155				},
156				complete: true,
157			},
158		},
159		{
160			"struct tags with ignored embed",
161			struct {
162				A       int `datastore:"a,noindex"`
163				B       int `datastore:"b"`
164				C       int `datastore:",noindex"`
165				D       int `datastore:""`
166				E       int
167				I       int `datastore:"-"`
168				J       int `datastore:",noindex" json:"j"`
169				oStruct `datastore:"-"`
170			}{},
171			&structCodec{
172				fields: map[string]fieldCodec{
173					"a": {path: []int{0}, noIndex: true},
174					"b": {path: []int{1}},
175					"C": {path: []int{2}, noIndex: true},
176					"D": {path: []int{3}},
177					"E": {path: []int{4}},
178					"J": {path: []int{6}, noIndex: true},
179				},
180				complete: true,
181			},
182		},
183		{
184			"unexported fields",
185			struct {
186				A int
187				b int
188				C int `datastore:"x"`
189				d int `datastore:"Y"`
190			}{},
191			&structCodec{
192				fields: map[string]fieldCodec{
193					"A": {path: []int{0}},
194					"x": {path: []int{2}},
195				},
196				complete: true,
197			},
198		},
199		{
200			"nested and embedded structs",
201			struct {
202				A   int
203				B   int
204				CC  oStruct
205				DDD rStruct
206				oStruct
207			}{},
208			&structCodec{
209				fields: map[string]fieldCodec{
210					"A":   {path: []int{0}},
211					"B":   {path: []int{1}},
212					"CC":  {path: []int{2}, structCodec: oStructCodec},
213					"DDD": {path: []int{3}, structCodec: rStructCodec},
214					"O":   {path: []int{4, 0}},
215				},
216				complete: true,
217			},
218		},
219		{
220			"struct tags with nested and embedded structs",
221			struct {
222				A       int     `datastore:"-"`
223				B       int     `datastore:"w"`
224				C       oStruct `datastore:"xx"`
225				D       rStruct `datastore:"y"`
226				oStruct `datastore:"z"`
227			}{},
228			&structCodec{
229				fields: map[string]fieldCodec{
230					"w":   {path: []int{1}},
231					"xx":  {path: []int{2}, structCodec: oStructCodec},
232					"y":   {path: []int{3}, structCodec: rStructCodec},
233					"z.O": {path: []int{4, 0}},
234				},
235				complete: true,
236			},
237		},
238		{
239			"unexported nested and embedded structs",
240			struct {
241				a int
242				B int
243				c uStruct
244				D uStruct
245				uStruct
246			}{},
247			&structCodec{
248				fields: map[string]fieldCodec{
249					"B": {path: []int{1}},
250					"D": {path: []int{3}, structCodec: uStructCodec},
251					"U": {path: []int{4, 0}},
252				},
253				complete: true,
254			},
255		},
256		{
257			"noindex nested struct",
258			struct {
259				A oStruct `datastore:",noindex"`
260			}{},
261			&structCodec{
262				fields: map[string]fieldCodec{
263					"A": {path: []int{0}, structCodec: oStructCodec, noIndex: true},
264				},
265				complete: true,
266			},
267		},
268		{
269			"noindex slice",
270			struct {
271				A []string `datastore:",noindex"`
272			}{},
273			&structCodec{
274				fields: map[string]fieldCodec{
275					"A": {path: []int{0}, noIndex: true},
276				},
277				hasSlice: true,
278				complete: true,
279			},
280		},
281		{
282			"noindex embedded struct slice",
283			struct {
284				// vStruct has a single field, V, also with noindex.
285				A []vStruct `datastore:",noindex"`
286			}{},
287			&structCodec{
288				fields: map[string]fieldCodec{
289					"A": {path: []int{0}, structCodec: vStructCodec, noIndex: true},
290				},
291				hasSlice: true,
292				complete: true,
293			},
294		},
295	}
296
297	for _, tc := range testCases {
298		got, err := getStructCodec(reflect.TypeOf(tc.structValue))
299		if err != nil {
300			t.Errorf("%s: getStructCodec: %v", tc.desc, err)
301			continue
302		}
303		// can't reflect.DeepEqual b/c element order in fields map may differ
304		if !isEqualStructCodec(got, tc.want) {
305			t.Errorf("%s\ngot  %+v\nwant %+v\n", tc.desc, got, tc.want)
306		}
307	}
308}
309
310func isEqualStructCodec(got, want *structCodec) bool {
311	if got.complete != want.complete {
312		return false
313	}
314	if got.hasSlice != want.hasSlice {
315		return false
316	}
317	if len(got.fields) != len(want.fields) {
318		return false
319	}
320	for name, wantF := range want.fields {
321		gotF := got.fields[name]
322		if !reflect.DeepEqual(wantF.path, gotF.path) {
323			return false
324		}
325		if wantF.noIndex != gotF.noIndex {
326			return false
327		}
328		if wantF.structCodec != nil {
329			if gotF.structCodec == nil {
330				return false
331			}
332			if !isEqualStructCodec(gotF.structCodec, wantF.structCodec) {
333				return false
334			}
335		}
336	}
337
338	return true
339}
340
341func TestRepeatedPropertyName(t *testing.T) {
342	good := []interface{}{
343		struct {
344			A int `datastore:"-"`
345		}{},
346		struct {
347			A int `datastore:"b"`
348			B int
349		}{},
350		struct {
351			A int
352			B int `datastore:"B"`
353		}{},
354		struct {
355			A int `datastore:"B"`
356			B int `datastore:"-"`
357		}{},
358		struct {
359			A int `datastore:"-"`
360			B int `datastore:"A"`
361		}{},
362		struct {
363			A int `datastore:"B"`
364			B int `datastore:"A"`
365		}{},
366		struct {
367			A int `datastore:"B"`
368			B int `datastore:"C"`
369			C int `datastore:"A"`
370		}{},
371		struct {
372			A int `datastore:"B"`
373			B int `datastore:"C"`
374			C int `datastore:"D"`
375		}{},
376	}
377	bad := []interface{}{
378		struct {
379			A int `datastore:"B"`
380			B int
381		}{},
382		struct {
383			A int
384			B int `datastore:"A"`
385		}{},
386		struct {
387			A int `datastore:"C"`
388			B int `datastore:"C"`
389		}{},
390		struct {
391			A int `datastore:"B"`
392			B int `datastore:"C"`
393			C int `datastore:"B"`
394		}{},
395	}
396	testGetStructCodec(t, good, bad)
397}
398
399func TestFlatteningNestedStructs(t *testing.T) {
400	type DeepGood struct {
401		A struct {
402			B []struct {
403				C struct {
404					D int
405				}
406			}
407		}
408	}
409	type DeepBad struct {
410		A struct {
411			B []struct {
412				C struct {
413					D []int
414				}
415			}
416		}
417	}
418	type ISay struct {
419		Tomato int
420	}
421	type YouSay struct {
422		Tomato int
423	}
424	type Tweedledee struct {
425		Dee int `datastore:"D"`
426	}
427	type Tweedledum struct {
428		Dum int `datastore:"D"`
429	}
430
431	good := []interface{}{
432		struct {
433			X []struct {
434				Y string
435			}
436		}{},
437		struct {
438			X []struct {
439				Y []byte
440			}
441		}{},
442		struct {
443			P []int
444			X struct {
445				Y []int
446			}
447		}{},
448		struct {
449			X struct {
450				Y []int
451			}
452			Q []int
453		}{},
454		struct {
455			P []int
456			X struct {
457				Y []int
458			}
459			Q []int
460		}{},
461		struct {
462			DeepGood
463		}{},
464		struct {
465			DG DeepGood
466		}{},
467		struct {
468			Foo struct {
469				Z int
470			} `datastore:"A"`
471			Bar struct {
472				Z int
473			} `datastore:"B"`
474		}{},
475	}
476	bad := []interface{}{
477		struct {
478			X []struct {
479				Y []string
480			}
481		}{},
482		struct {
483			X []struct {
484				Y []int
485			}
486		}{},
487		struct {
488			DeepBad
489		}{},
490		struct {
491			DB DeepBad
492		}{},
493		struct {
494			ISay
495			YouSay
496		}{},
497		struct {
498			Tweedledee
499			Tweedledum
500		}{},
501		struct {
502			Foo struct {
503				Z int
504			} `datastore:"A"`
505			Bar struct {
506				Z int
507			} `datastore:"A"`
508		}{},
509	}
510	testGetStructCodec(t, good, bad)
511}
512
513func testGetStructCodec(t *testing.T, good []interface{}, bad []interface{}) {
514	for _, x := range good {
515		if _, err := getStructCodec(reflect.TypeOf(x)); err != nil {
516			t.Errorf("type %T: got non-nil error (%s), want nil", x, err)
517		}
518	}
519	for _, x := range bad {
520		if _, err := getStructCodec(reflect.TypeOf(x)); err == nil {
521			t.Errorf("type %T: got nil error, want non-nil", x)
522		}
523	}
524}
525
526func TestNilKeyIsStored(t *testing.T) {
527	x := struct {
528		K *Key
529		I int
530	}{}
531	p := PropertyList{}
532	// Save x as properties.
533	p1, _ := SaveStruct(&x)
534	p.Load(p1)
535	// Set x's fields to non-zero.
536	x.K = &Key{}
537	x.I = 2
538	// Load x from properties.
539	p2, _ := p.Save()
540	LoadStruct(&x, p2)
541	// Check that x's fields were set to zero.
542	if x.K != nil {
543		t.Errorf("K field was not zero")
544	}
545	if x.I != 0 {
546		t.Errorf("I field was not zero")
547	}
548}
549
550func TestSaveStructOmitEmpty(t *testing.T) {
551	// Expected props names are sorted alphabetically
552	expectedPropNamesForSingles := []string{"EmptyValue", "NonEmptyValue", "OmitEmptyWithValue"}
553	expectedPropNamesForSlices := []string{"NonEmptyValue", "NonEmptyValue", "OmitEmptyWithValue", "OmitEmptyWithValue"}
554
555	testOmitted := func(expectedPropNames []string, src interface{}) {
556		// t.Helper() - this is available from Go version 1.9, but we also support Go versions 1.6, 1.7, 1.8
557		if props, err := SaveStruct(src); err != nil {
558			t.Fatal(err)
559		} else {
560			// Collect names for reporting if diffs from expected and for easier sorting
561			actualPropNames := make([]string, len(props))
562			for i := range props {
563				actualPropNames[i] = props[i].Name
564			}
565			// Sort actuals for comparing with already sorted expected names
566			sort.Sort(sort.StringSlice(actualPropNames))
567			if !reflect.DeepEqual(actualPropNames, expectedPropNames) {
568				t.Errorf("Expected this properties: %v, got: %v", expectedPropNames, actualPropNames)
569			}
570		}
571	}
572
573	testOmitted(expectedPropNamesForSingles, &struct {
574		EmptyValue         int
575		NonEmptyValue      int
576		OmitEmptyNoValue   int `datastore:",omitempty"`
577		OmitEmptyWithValue int `datastore:",omitempty"`
578	}{
579		NonEmptyValue:      1,
580		OmitEmptyWithValue: 2,
581	})
582
583	testOmitted(expectedPropNamesForSlices, &struct {
584		EmptyValue         []int
585		NonEmptyValue      []int
586		OmitEmptyNoValue   []int `datastore:",omitempty"`
587		OmitEmptyWithValue []int `datastore:",omitempty"`
588	}{
589		NonEmptyValue:      []int{1, 2},
590		OmitEmptyWithValue: []int{3, 4},
591	})
592
593	testOmitted(expectedPropNamesForSingles, &struct {
594		EmptyValue         bool
595		NonEmptyValue      bool
596		OmitEmptyNoValue   bool `datastore:",omitempty"`
597		OmitEmptyWithValue bool `datastore:",omitempty"`
598	}{
599		NonEmptyValue:      true,
600		OmitEmptyWithValue: true,
601	})
602
603	testOmitted(expectedPropNamesForSlices, &struct {
604		EmptyValue         []bool
605		NonEmptyValue      []bool
606		OmitEmptyNoValue   []bool `datastore:",omitempty"`
607		OmitEmptyWithValue []bool `datastore:",omitempty"`
608	}{
609		NonEmptyValue:      []bool{true, true},
610		OmitEmptyWithValue: []bool{true, true},
611	})
612
613	testOmitted(expectedPropNamesForSingles, &struct {
614		EmptyValue         string
615		NonEmptyValue      string
616		OmitEmptyNoValue   string `datastore:",omitempty"`
617		OmitEmptyWithValue string `datastore:",omitempty"`
618	}{
619		NonEmptyValue:      "s",
620		OmitEmptyWithValue: "s",
621	})
622
623	testOmitted(expectedPropNamesForSlices, &struct {
624		EmptyValue         []string
625		NonEmptyValue      []string
626		OmitEmptyNoValue   []string `datastore:",omitempty"`
627		OmitEmptyWithValue []string `datastore:",omitempty"`
628	}{
629		NonEmptyValue:      []string{"s1", "s2"},
630		OmitEmptyWithValue: []string{"s3", "s4"},
631	})
632
633	testOmitted(expectedPropNamesForSingles, &struct {
634		EmptyValue         float32
635		NonEmptyValue      float32
636		OmitEmptyNoValue   float32 `datastore:",omitempty"`
637		OmitEmptyWithValue float32 `datastore:",omitempty"`
638	}{
639		NonEmptyValue:      1.1,
640		OmitEmptyWithValue: 1.2,
641	})
642
643	testOmitted(expectedPropNamesForSlices, &struct {
644		EmptyValue         []float32
645		NonEmptyValue      []float32
646		OmitEmptyNoValue   []float32 `datastore:",omitempty"`
647		OmitEmptyWithValue []float32 `datastore:",omitempty"`
648	}{
649		NonEmptyValue:      []float32{1.1, 2.2},
650		OmitEmptyWithValue: []float32{3.3, 4.4},
651	})
652
653	testOmitted(expectedPropNamesForSingles, &struct {
654		EmptyValue         time.Time
655		NonEmptyValue      time.Time
656		OmitEmptyNoValue   time.Time `datastore:",omitempty"`
657		OmitEmptyWithValue time.Time `datastore:",omitempty"`
658	}{
659		NonEmptyValue:      now,
660		OmitEmptyWithValue: now,
661	})
662
663	testOmitted(expectedPropNamesForSlices, &struct {
664		EmptyValue         []time.Time
665		NonEmptyValue      []time.Time
666		OmitEmptyNoValue   []time.Time `datastore:",omitempty"`
667		OmitEmptyWithValue []time.Time `datastore:",omitempty"`
668	}{
669		NonEmptyValue:      []time.Time{now, now},
670		OmitEmptyWithValue: []time.Time{now, now},
671	})
672}
673