| Index: go/src/infra/gae/libs/gae/helper/datastore_key_test.go
 | 
| diff --git a/go/src/infra/gae/libs/gae/helper/datastore_key_test.go b/go/src/infra/gae/libs/gae/helper/datastore_key_test.go
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..81b131316a1daa051a098b6f06dd53934fb2eda9
 | 
| --- /dev/null
 | 
| +++ b/go/src/infra/gae/libs/gae/helper/datastore_key_test.go
 | 
| @@ -0,0 +1,241 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package helper
 | 
| +
 | 
| +import (
 | 
| +	"encoding/json"
 | 
| +	"fmt"
 | 
| +	"testing"
 | 
| +
 | 
| +	"infra/gae/libs/gae"
 | 
| +
 | 
| +	. "github.com/smartystreets/goconvey/convey"
 | 
| +)
 | 
| +
 | 
| +func mkKey(aid, ns string, elems ...interface{}) gae.DSKey {
 | 
| +	if len(elems)%2 != 0 {
 | 
| +		panic("odd number of tokens")
 | 
| +	}
 | 
| +	toks := make([]gae.DSKeyTok, len(elems)/2)
 | 
| +	for i := 0; i < len(elems); i += 2 {
 | 
| +		toks[i/2].Kind = elems[i].(string)
 | 
| +		switch x := elems[i+1].(type) {
 | 
| +		case string:
 | 
| +			toks[i/2].StringID = x
 | 
| +		case int:
 | 
| +			toks[i/2].IntID = int64(x)
 | 
| +		default:
 | 
| +			panic("bad token id")
 | 
| +		}
 | 
| +	}
 | 
| +	return NewDSKeyToks(aid, ns, toks)
 | 
| +}
 | 
| +
 | 
| +func ShouldEqualKey(actual interface{}, expected ...interface{}) string {
 | 
| +	if len(expected) != 1 {
 | 
| +		return fmt.Sprintf("Assertion requires 1 expected value, got %d", len(expected))
 | 
| +	}
 | 
| +	if DSKeysEqual(actual.(gae.DSKey), expected[0].(gae.DSKey)) {
 | 
| +		return ""
 | 
| +	}
 | 
| +	return fmt.Sprintf("Expected: %q\nActual: %q", actual, expected[0])
 | 
| +}
 | 
| +
 | 
| +func TestKeyEncode(t *testing.T) {
 | 
| +	t.Parallel()
 | 
| +
 | 
| +	keys := []gae.DSKey{
 | 
| +		mkKey("appid", "ns", "kind", 1),
 | 
| +		mkKey("appid", "ns", "nerd", "moo"),
 | 
| +		mkKey("appid", "ns", "parent", 10, "renerd", "moo"),
 | 
| +	}
 | 
| +
 | 
| +	Convey("DSKey Round trip", t, func() {
 | 
| +		for _, k := range keys {
 | 
| +			k := k
 | 
| +			Convey(k.String(), func() {
 | 
| +				enc := DSKeyEncode(k)
 | 
| +				aid, ns, toks, err := DSKeyToksDecode(enc)
 | 
| +				So(err, ShouldBeNil)
 | 
| +				dec := NewDSKeyToks(aid, ns, toks)
 | 
| +				So(dec, ShouldNotBeNil)
 | 
| +				So(dec, ShouldEqualKey, k)
 | 
| +			})
 | 
| +
 | 
| +			Convey(k.String()+" (json)", func() {
 | 
| +				data, err := DSKeyMarshalJSON(k)
 | 
| +				So(err, ShouldBeNil)
 | 
| +
 | 
| +				aid, ns, toks, err := DSKeyUnmarshalJSON(data)
 | 
| +				So(err, ShouldBeNil)
 | 
| +				So(NewDSKeyToks(aid, ns, toks), ShouldEqualKey, k)
 | 
| +			})
 | 
| +		}
 | 
| +	})
 | 
| +
 | 
| +	Convey("NewDSKey", t, func() {
 | 
| +		Convey("single", func() {
 | 
| +			k := NewDSKey("appid", "ns", "kind", "", 1, nil)
 | 
| +			So(k, ShouldEqualKey, keys[0])
 | 
| +		})
 | 
| +
 | 
| +		Convey("nest", func() {
 | 
| +			k := NewDSKey("appid", "ns", "renerd", "moo", 0,
 | 
| +				NewDSKey("appid", "ns", "parent", "", 10, nil))
 | 
| +			So(k, ShouldEqualKey, keys[2])
 | 
| +		})
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKey bad encoding", t, func() {
 | 
| +		Convey("extra junk before", func() {
 | 
| +			enc := DSKeyEncode(keys[2])
 | 
| +			_, _, _, err := DSKeyToksDecode("/" + enc)
 | 
| +			So(err, ShouldErrLike, "illegal base64")
 | 
| +		})
 | 
| +
 | 
| +		Convey("extra junk after", func() {
 | 
| +			enc := DSKeyEncode(keys[2])
 | 
| +			_, _, _, err := DSKeyToksDecode(enc[:len(enc)-1])
 | 
| +			So(err, ShouldErrLike, "EOF")
 | 
| +		})
 | 
| +
 | 
| +		Convey("json encoding includes quotes", func() {
 | 
| +			data, err := DSKeyMarshalJSON(keys[0])
 | 
| +			So(err, ShouldBeNil)
 | 
| +
 | 
| +			_, _, _, err = DSKeyUnmarshalJSON(append(data, '!'))
 | 
| +			So(err, ShouldErrLike, "bad JSON key")
 | 
| +		})
 | 
| +	})
 | 
| +}
 | 
| +
 | 
| +type dumbKey1 struct{ gae.DSKey }
 | 
| +
 | 
| +func (dk dumbKey1) Namespace() string { return "ns" }
 | 
| +func (dk dumbKey1) Parent() gae.DSKey { return dk.DSKey }
 | 
| +func (dk dumbKey1) String() string    { return "dumbKey1" }
 | 
| +
 | 
| +type dumbKey2 struct{ gae.DSKey }
 | 
| +
 | 
| +/// This is the dumb part... can't have both IDs set.
 | 
| +func (dk dumbKey2) IntID() int64     { return 1 }
 | 
| +func (dk dumbKey2) StringID() string { return "wat" }
 | 
| +
 | 
| +func (dk dumbKey2) Kind() string      { return "kind" }
 | 
| +func (dk dumbKey2) Parent() gae.DSKey { return nil }
 | 
| +func (dk dumbKey2) Namespace() string { return "ns" }
 | 
| +func (dk dumbKey2) AppID() string     { return "aid" }
 | 
| +func (dk dumbKey2) String() string    { return "dumbKey2" }
 | 
| +
 | 
| +func TestBadKeyEncode(t *testing.T) {
 | 
| +	t.Parallel()
 | 
| +
 | 
| +	Convey("bad keys", t, func() {
 | 
| +		Convey("incomplete", func() {
 | 
| +			So(DSKeyIncomplete(mkKey("aid", "ns", "kind", 1)), ShouldBeFalse)
 | 
| +			So(DSKeyIncomplete(mkKey("aid", "ns", "kind", 0)), ShouldBeTrue)
 | 
| +		})
 | 
| +
 | 
| +		Convey("invalid", func() {
 | 
| +			So(DSKeyValid(mkKey("aid", "ns", "hat", "face", "__kind__", 1), "ns", true), ShouldBeTrue)
 | 
| +			So(DSKeyValid(mkKey("aid", "ns", "hat", "face", "kind", 1), "wat", false), ShouldBeFalse)
 | 
| +
 | 
| +			bads := []gae.DSKey{
 | 
| +				nil,
 | 
| +				mkKey("", "ns", "hat", "face"),
 | 
| +				mkKey("aid", "ns", "base", 1, "", "id"),
 | 
| +				mkKey("aid", "ns", "hat", "face", "__kind__", 1),
 | 
| +				mkKey("aid", "ns", "hat", 0, "kind", 1),
 | 
| +				dumbKey1{mkKey("aid", "badNS", "hat", 1)},
 | 
| +				dumbKey2{},
 | 
| +			}
 | 
| +			for _, k := range bads {
 | 
| +				s := "<nil>"
 | 
| +				if k != nil {
 | 
| +					s = k.String()
 | 
| +				}
 | 
| +				Convey(s, func() {
 | 
| +					So(DSKeyValid(k, "ns", false), ShouldBeFalse)
 | 
| +				})
 | 
| +			}
 | 
| +		})
 | 
| +	})
 | 
| +}
 | 
| +
 | 
| +type keyWrap struct{ gae.DSKey }
 | 
| +
 | 
| +func (k keyWrap) Parent() gae.DSKey {
 | 
| +	if k.DSKey.Parent() != nil {
 | 
| +		return keyWrap{k.DSKey.Parent()}
 | 
| +	}
 | 
| +	return nil
 | 
| +}
 | 
| +
 | 
| +func TestMiscKey(t *testing.T) {
 | 
| +	t.Parallel()
 | 
| +
 | 
| +	Convey("DSKeyRoot", t, func() {
 | 
| +		k := mkKey("appid", "ns", "parent", 10, "renerd", "moo")
 | 
| +		r := mkKey("appid", "ns", "parent", 10)
 | 
| +		So(DSKeyRoot(k), ShouldEqualKey, r)
 | 
| +		So(DSKeyRoot(nil), ShouldBeNil)
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKeySplit", t, func() {
 | 
| +		// keyWrap forces DSKeySplit to not take the GenericDSKey shortcut.
 | 
| +		k := keyWrap{mkKey("appid", "ns", "parent", 10, "renerd", "moo")}
 | 
| +		aid, ns, toks := DSKeySplit(k)
 | 
| +		So(aid, ShouldEqual, "appid")
 | 
| +		So(ns, ShouldEqual, "ns")
 | 
| +		So(toks, ShouldResemble, []gae.DSKeyTok{
 | 
| +			{Kind: "parent", IntID: 10},
 | 
| +			{Kind: "renerd", StringID: "moo"},
 | 
| +		})
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKeySplit (nil)", t, func() {
 | 
| +		aid, ns, toks := DSKeySplit(nil)
 | 
| +		So(aid, ShouldEqual, "")
 | 
| +		So(ns, ShouldEqual, "")
 | 
| +		So(toks, ShouldResemble, []gae.DSKeyTok(nil))
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKeySplit ((*GenericDSKey)(nil))", t, func() {
 | 
| +		aid, ns, toks := DSKeySplit((*GenericDSKey)(nil))
 | 
| +		So(aid, ShouldEqual, "")
 | 
| +		So(ns, ShouldEqual, "")
 | 
| +		So(toks, ShouldResemble, []gae.DSKeyTok(nil))
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKeysEqual", t, func() {
 | 
| +		k1 := mkKey("a", "n", "knd", 1)
 | 
| +		k2 := mkKey("a", "n", "knd", 1)
 | 
| +		So(DSKeysEqual(k1, k2), ShouldBeTrue)
 | 
| +		k3 := mkKey("a", "n", "knd", 2)
 | 
| +		So(DSKeysEqual(k1, k3), ShouldBeFalse)
 | 
| +	})
 | 
| +
 | 
| +	Convey("DSKeyString", t, func() {
 | 
| +		k1 := mkKey("a", "n", "knd", 1, "other", "wat")
 | 
| +		So(DSKeyString(k1), ShouldEqual, "/knd,1/other,wat")
 | 
| +		So(DSKeyString(nil), ShouldEqual, "")
 | 
| +	})
 | 
| +
 | 
| +	Convey("*GenericDSKey supports json encoding", t, func() {
 | 
| +		type TestStruct struct {
 | 
| +			Key *GenericDSKey
 | 
| +		}
 | 
| +		t := &TestStruct{
 | 
| +			NewDSKey("aid", "ns", "kind", "id", 0,
 | 
| +				NewDSKey("aid", "ns", "parent", "", 1, nil),
 | 
| +			)}
 | 
| +		d, err := json.Marshal(t)
 | 
| +		So(err, ShouldBeNil)
 | 
| +		t2 := &TestStruct{}
 | 
| +		err = json.Unmarshal(d, t2)
 | 
| +		So(err, ShouldBeNil)
 | 
| +		So(t, ShouldResemble, t2)
 | 
| +	})
 | 
| +}
 | 
| 
 |