blob: 46029bba51d7659c6d884315d6d6a66432538203 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package datastore
import (
"reflect"
"testing"
proto "github.com/golang/protobuf/proto"
pb "google.golang.org/appengine/internal/datastore"
)
type Simple struct {
I int64
}
type SimpleWithTag struct {
I int64 `datastore:"II"`
}
type NestedSimpleWithTag struct {
A SimpleWithTag `datastore:"AA"`
}
type NestedSliceOfSimple struct {
A []Simple
}
type SimpleTwoFields struct {
S string
SS string
}
type NestedSimpleAnonymous struct {
Simple
X string
}
type NestedSimple struct {
A Simple
I int64
}
type NestedSimple1 struct {
A Simple
X string
}
type NestedSimple2X struct {
AA NestedSimple
A SimpleTwoFields
S string
}
type BDotB struct {
B string `datastore:"B.B"`
}
type ABDotB struct {
A BDotB
}
type MultiAnonymous struct {
Simple
SimpleTwoFields
X string
}
var (
// these values need to be addressable
testString2 = "two"
testString3 = "three"
testInt64 = int64(2)
fieldNameI = "I"
fieldNameX = "X"
fieldNameS = "S"
fieldNameSS = "SS"
fieldNameADotI = "A.I"
fieldNameAADotII = "AA.II"
fieldNameADotBDotB = "A.B.B"
)
func TestLoadEntityNestedLegacy(t *testing.T) {
testCases := []struct {
desc string
src *pb.EntityProto
want interface{}
}{
{
"nested",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
&pb.Property{
Name: &fieldNameADotI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
},
},
&NestedSimple1{
A: Simple{I: testInt64},
X: testString2,
},
},
{
"nested with tag",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameAADotII,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
},
},
&NestedSimpleWithTag{
A: SimpleWithTag{I: testInt64},
},
},
{
"nested with anonymous struct field",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
},
},
&NestedSimpleAnonymous{
Simple: Simple{I: testInt64},
X: testString2,
},
},
{
"nested with dotted field tag",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameADotBDotB,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
},
},
&ABDotB{
A: BDotB{
B: testString2,
},
},
},
{
"nested with dotted field tag",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
&pb.Property{
Name: &fieldNameS,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
&pb.Property{
Name: &fieldNameSS,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
},
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
},
},
},
&MultiAnonymous{
Simple: Simple{I: testInt64},
SimpleTwoFields: SimpleTwoFields{S: "two", SS: "three"},
X: "three",
},
},
}
for _, tc := range testCases {
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
err := loadEntity(dst, tc.src)
if err != nil {
t.Errorf("loadEntity: %s: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(tc.want, dst) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
}
}
}
type WithKey struct {
X string
I int64
K *Key `datastore:"__key__"`
}
type NestedWithKey struct {
N WithKey
Y string
}
var (
incompleteKey = newKey("", nil)
invalidKey = newKey("s", incompleteKey)
// these values need to be addressable
fieldNameA = "A"
fieldNameK = "K"
fieldNameN = "N"
fieldNameY = "Y"
fieldNameAA = "AA"
fieldNameII = "II"
fieldNameBDotB = "B.B"
entityProtoMeaning = pb.Property_ENTITY_PROTO
TRUE = true
FALSE = false
)
var (
simpleEntityProto, nestedSimpleEntityProto,
simpleTwoFieldsEntityProto, simpleWithTagEntityProto,
bDotBEntityProto, withKeyEntityProto string
)
func init() {
// simpleEntityProto corresponds to:
// Simple{I: testInt64}
simpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", incompleteKey),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
simpleEntityProto = string(simpleEntityProtob)
// nestedSimpleEntityProto corresponds to:
// NestedSimple{
// A: Simple{I: testInt64},
// I: testInt64,
// }
nestedSimpleEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", incompleteKey),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &simpleEntityProto,
},
Multiple: &FALSE,
},
&pb.Property{
Name: &fieldNameI,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
nestedSimpleEntityProto = string(nestedSimpleEntityProtob)
// simpleTwoFieldsEntityProto corresponds to:
// SimpleTwoFields{S: testString2, SS: testString3}
simpleTwoFieldsEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", incompleteKey),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameS,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
Multiple: &FALSE,
},
&pb.Property{
Name: &fieldNameSS,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
simpleTwoFieldsEntityProto = string(simpleTwoFieldsEntityProtob)
// simpleWithTagEntityProto corresponds to:
// SimpleWithTag{I: testInt64}
simpleWithTagEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", incompleteKey),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameII,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
simpleWithTagEntityProto = string(simpleWithTagEntityProtob)
// bDotBEntityProto corresponds to:
// BDotB{
// B: testString2,
// }
bDotBEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", incompleteKey),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameBDotB,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
bDotBEntityProto = string(bDotBEntityProtob)
// withKeyEntityProto corresponds to:
// WithKey{
// X: testString3,
// I: testInt64,
// K: testKey1a,
// }
withKeyEntityProtob, err := proto.Marshal(&pb.EntityProto{
Key: keyToProto("", testKey1a),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
Multiple: &FALSE,
},
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
Multiple: &FALSE,
},
},
EntityGroup: &pb.Path{},
})
if err != nil {
panic(err)
}
withKeyEntityProto = string(withKeyEntityProtob)
}
func TestLoadEntityNested(t *testing.T) {
testCases := []struct {
desc string
src *pb.EntityProto
want interface{}
}{
{
"nested basic",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &simpleEntityProto,
},
},
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
},
},
&NestedSimple{
A: Simple{I: 2},
I: 2,
},
},
{
"nested with struct tags",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameAA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &simpleWithTagEntityProto,
},
},
},
},
&NestedSimpleWithTag{
A: SimpleWithTag{I: testInt64},
},
},
{
"nested 2x",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameAA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &nestedSimpleEntityProto,
},
},
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &simpleTwoFieldsEntityProto,
},
},
&pb.Property{
Name: &fieldNameS,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
},
},
},
&NestedSimple2X{
AA: NestedSimple{
A: Simple{I: testInt64},
I: testInt64,
},
A: SimpleTwoFields{S: testString2, SS: testString3},
S: testString3,
},
},
{
"nested anonymous",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
},
},
&NestedSimpleAnonymous{
Simple: Simple{I: testInt64},
X: testString2,
},
},
{
"nested simple with slice",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Multiple: &TRUE,
Value: &pb.PropertyValue{
StringValue: &simpleEntityProto,
},
},
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Multiple: &TRUE,
Value: &pb.PropertyValue{
StringValue: &simpleEntityProto,
},
},
},
},
&NestedSliceOfSimple{
A: []Simple{Simple{I: testInt64}, Simple{I: testInt64}},
},
},
{
"nested with multiple anonymous fields",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameI,
Value: &pb.PropertyValue{
Int64Value: &testInt64,
},
},
&pb.Property{
Name: &fieldNameS,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
&pb.Property{
Name: &fieldNameSS,
Value: &pb.PropertyValue{
StringValue: &testString3,
},
},
&pb.Property{
Name: &fieldNameX,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
},
},
&MultiAnonymous{
Simple: Simple{I: testInt64},
SimpleTwoFields: SimpleTwoFields{S: testString2, SS: testString3},
X: testString2,
},
},
{
"nested with dotted field tag",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameA,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &bDotBEntityProto,
},
},
},
},
&ABDotB{
A: BDotB{
B: testString2,
},
},
},
{
"nested entity with key",
&pb.EntityProto{
Key: keyToProto("some-app-id", testKey0),
Property: []*pb.Property{
&pb.Property{
Name: &fieldNameY,
Value: &pb.PropertyValue{
StringValue: &testString2,
},
},
&pb.Property{
Name: &fieldNameN,
Meaning: &entityProtoMeaning,
Value: &pb.PropertyValue{
StringValue: &withKeyEntityProto,
},
},
},
},
&NestedWithKey{
Y: testString2,
N: WithKey{
X: testString3,
I: testInt64,
K: testKey1a,
},
},
},
}
for _, tc := range testCases {
dst := reflect.New(reflect.TypeOf(tc.want).Elem()).Interface()
err := loadEntity(dst, tc.src)
if err != nil {
t.Errorf("loadEntity: %s: %v", tc.desc, err)
continue
}
if !reflect.DeepEqual(tc.want, dst) {
t.Errorf("%s: compare:\ngot: %#v\nwant: %#v", tc.desc, dst, tc.want)
}
}
}