// 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) } } }