go.mercari.io/datastore@v1.8.2/boom/boom_test.go (about)

     1  package boom
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  
    10  	"go.mercari.io/datastore"
    11  	"go.mercari.io/datastore/internal/testutils"
    12  )
    13  
    14  var _ datastore.PropertyTranslator = UserID(0)
    15  var _ datastore.PropertyTranslator = DataID(0)
    16  var _ datastore.PropertyTranslator = WithAncestorID("")
    17  var _ datastore.PropertyTranslator = IntID(0)
    18  var _ datastore.PropertyTranslator = StringID("")
    19  
    20  type contextClient struct{}
    21  
    22  type UserID int64
    23  type DataID int64
    24  type WithAncestorID string
    25  
    26  type IntID int64
    27  type StringID string
    28  
    29  func (id UserID) ToPropertyValue(ctx context.Context) (interface{}, error) {
    30  	client := ctx.Value(contextClient{}).(datastore.Client)
    31  	key := client.IDKey("User", int64(id), nil)
    32  	return key, nil
    33  }
    34  
    35  func (id UserID) FromPropertyValue(ctx context.Context, p datastore.Property) (dst interface{}, err error) {
    36  	key, ok := p.Value.(datastore.Key)
    37  	if !ok {
    38  		return nil, datastore.ErrInvalidEntityType
    39  	}
    40  	return UserID(key.ID()), nil
    41  }
    42  
    43  func (id DataID) ToPropertyValue(ctx context.Context) (interface{}, error) {
    44  	client := ctx.Value(contextClient{}).(datastore.Client)
    45  	key := client.IDKey("Data", int64(id), nil)
    46  	return key, nil
    47  }
    48  
    49  func (id DataID) FromPropertyValue(ctx context.Context, p datastore.Property) (dst interface{}, err error) {
    50  	key, ok := p.Value.(datastore.Key)
    51  	if !ok {
    52  		return nil, datastore.ErrInvalidEntityType
    53  	}
    54  	return DataID(key.ID()), nil
    55  }
    56  
    57  func (id WithAncestorID) ToPropertyValue(ctx context.Context) (interface{}, error) {
    58  	client := ctx.Value(contextClient{}).(datastore.Client)
    59  	ss := strings.SplitN(string(id), "-", 2)
    60  	if len(ss) != 2 {
    61  		return nil, fmt.Errorf("unexpected id format: %s", id)
    62  	}
    63  	userID, err := strconv.ParseInt(ss[0], 10, 64)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	dataID, err := strconv.ParseInt(ss[1], 10, 64)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	parentKey := client.IDKey("User", userID, nil)
    72  	key := client.IDKey("Data", dataID, parentKey)
    73  	return key, nil
    74  }
    75  
    76  func (id WithAncestorID) FromPropertyValue(ctx context.Context, p datastore.Property) (dst interface{}, err error) {
    77  	key, ok := p.Value.(datastore.Key)
    78  	if !ok {
    79  		return nil, datastore.ErrInvalidEntityType
    80  	}
    81  	userID := key.ParentKey().ID()
    82  	dataID := key.ID()
    83  	return WithAncestorID(fmt.Sprintf("%d-%d", userID, dataID)), nil
    84  }
    85  
    86  func (id IntID) ToPropertyValue(ctx context.Context) (interface{}, error) {
    87  	// for boom.KeyError
    88  	return int64(id), nil
    89  }
    90  
    91  func (id IntID) FromPropertyValue(ctx context.Context, p datastore.Property) (dst interface{}, err error) {
    92  	key, ok := p.Value.(datastore.Key)
    93  	if !ok {
    94  		return nil, datastore.ErrInvalidEntityType
    95  	}
    96  	return IntID(key.ID()), nil
    97  }
    98  
    99  func (id StringID) ToPropertyValue(ctx context.Context) (interface{}, error) {
   100  	// for boom.KeyError
   101  	return string(id), nil
   102  }
   103  
   104  func (id StringID) FromPropertyValue(ctx context.Context, p datastore.Property) (dst interface{}, err error) {
   105  	key, ok := p.Value.(datastore.Key)
   106  	if !ok {
   107  		return nil, datastore.ErrInvalidEntityType
   108  	}
   109  	return StringID(key.Name()), nil
   110  }
   111  
   112  func TestBoom_Key(t *testing.T) {
   113  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   114  	defer cleanUp()
   115  
   116  	type Data struct {
   117  		ID int64 `datastore:"-" boom:"id"`
   118  	}
   119  
   120  	bm := FromClient(ctx, client)
   121  
   122  	key := bm.Key(&Data{111})
   123  	if v := key.Kind(); v != "Data" {
   124  		t.Errorf("unexpected: %v", v)
   125  	}
   126  	if v := key.ID(); v != 111 {
   127  		t.Errorf("unexpected: %v", v)
   128  	}
   129  }
   130  
   131  func TestBoom_KeyWithPropertyTranslator(t *testing.T) {
   132  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   133  	defer cleanUp()
   134  
   135  	{ // IntID with PT
   136  		type Data struct {
   137  			ID IntID `datastore:"-" boom:"id"`
   138  		}
   139  
   140  		bm := FromClient(ctx, client)
   141  
   142  		_, err := bm.Put(&Data{111})
   143  		if err != nil {
   144  			t.Fatal(err)
   145  		}
   146  
   147  		err = bm.Get(&Data{111})
   148  		if err != nil {
   149  			t.Fatal(err)
   150  		}
   151  	}
   152  	{ // StringID with PT
   153  		type Data struct {
   154  			ID StringID `datastore:"-" boom:"id"`
   155  		}
   156  
   157  		bm := FromClient(ctx, client)
   158  
   159  		_, err := bm.Put(&Data{"a"})
   160  		if err != nil {
   161  			t.Fatal(err)
   162  		}
   163  
   164  		err = bm.Get(&Data{"a"})
   165  		if err != nil {
   166  			t.Fatal(err)
   167  		}
   168  	}
   169  }
   170  
   171  func TestBoom_KeyWithParent(t *testing.T) {
   172  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   173  	defer cleanUp()
   174  
   175  	type Data struct {
   176  		ParentKey datastore.Key `datastore:"-" boom:"parent"`
   177  		ID        int64         `datastore:"-" boom:"id"`
   178  	}
   179  
   180  	bm := FromClient(ctx, client)
   181  
   182  	userKey := client.NameKey("User", "test", nil)
   183  	key := bm.Key(&Data{userKey, 111})
   184  	if v := key.ParentKey().Kind(); v != "User" {
   185  		t.Errorf("unexpected: %v", v)
   186  	}
   187  	if v := key.ParentKey().Name(); v != "test" {
   188  		t.Errorf("unexpected: %v", v)
   189  	}
   190  	if v := key.Kind(); v != "Data" {
   191  		t.Errorf("unexpected: %v", v)
   192  	}
   193  	if v := key.ID(); v != 111 {
   194  		t.Errorf("unexpected: %v", v)
   195  	}
   196  }
   197  
   198  func TestBoom_AllocateID(t *testing.T) {
   199  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   200  	defer cleanUp()
   201  
   202  	type Data struct {
   203  		ID IntID `datastore:"-" boom:"id"`
   204  	}
   205  
   206  	bm := FromClient(ctx, client)
   207  
   208  	obj := &Data{}
   209  	key, err := bm.AllocateID(obj)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  
   214  	if v := key.Kind(); v != "Data" {
   215  		t.Errorf("unexpected: %v", v)
   216  	}
   217  	if v := key.ID(); v == 0 {
   218  		t.Errorf("unexpected: %v", v)
   219  	}
   220  	if v := int64(obj.ID); v != key.ID() {
   221  		t.Errorf("unexpected: %v", v)
   222  	}
   223  }
   224  
   225  func TestBoom_AllocateIDs(t *testing.T) {
   226  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   227  	defer cleanUp()
   228  
   229  	type Data struct {
   230  		ID IntID `datastore:"-" boom:"id"`
   231  	}
   232  
   233  	bm := FromClient(ctx, client)
   234  
   235  	type Spec struct {
   236  		From   interface{}
   237  		Kind   string
   238  		Assert func(key datastore.Key, spec Spec)
   239  	}
   240  
   241  	specs := []Spec{
   242  		// struct
   243  		{&Data{}, "Data", func(key datastore.Key, spec Spec) {
   244  			obj := spec.From.(*Data)
   245  			if v := int64(obj.ID); v != key.ID() {
   246  				t.Errorf("unexpected: %v", v)
   247  			}
   248  		}},
   249  		// key without parent
   250  		{client.IncompleteKey("User", nil), "User", nil},
   251  		// key with parent
   252  		{client.IncompleteKey("Todo", client.NameKey("User", "foo", nil)), "Todo", func(key datastore.Key, spec Spec) {
   253  			if v := key.ParentKey(); v == nil {
   254  				t.Fatalf("unexpected: %v", v)
   255  			}
   256  			if v := key.ParentKey().Kind(); v != "User" {
   257  				t.Errorf("unexpected: %v", v)
   258  			}
   259  			if v := key.ParentKey().Name(); v != "foo" {
   260  				t.Errorf("unexpected: %v", v)
   261  			}
   262  		}},
   263  		// string
   264  		{"Book", "Book", nil},
   265  	}
   266  
   267  	srcs := make([]interface{}, 0, len(specs))
   268  	for _, spec := range specs {
   269  		srcs = append(srcs, spec.From)
   270  	}
   271  
   272  	keys, err := bm.AllocateIDs(srcs)
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  
   277  	if v := len(keys); v != len(specs) {
   278  		t.Errorf("unexpected: %v", v)
   279  	}
   280  
   281  	for idx, spec := range specs {
   282  		key := keys[idx]
   283  		if v := key.Kind(); v != spec.Kind {
   284  			t.Errorf("unexpected: %v", v)
   285  		}
   286  		if v := key.ID(); v == 0 {
   287  			t.Errorf("unexpected: %v", v)
   288  		}
   289  		if spec.Assert != nil {
   290  			spec.Assert(key, spec)
   291  		}
   292  	}
   293  }
   294  
   295  func TestBoom_Put(t *testing.T) {
   296  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   297  	defer cleanUp()
   298  
   299  	type Data struct {
   300  		ID  int64  `datastore:"-" boom:"id"`
   301  		Str string ``
   302  	}
   303  
   304  	bm := FromClient(ctx, client)
   305  
   306  	key, err := bm.Put(&Data{111, "Str"})
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  
   311  	if v := key.Kind(); v != "Data" {
   312  		t.Errorf("unexpected: %v", v)
   313  	}
   314  	if v := key.ID(); v != 111 {
   315  		t.Errorf("unexpected: %v", v)
   316  	}
   317  
   318  	obj := &Data{}
   319  	err = client.Get(ctx, key, obj)
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	if v := obj.Str; v != "Str" {
   325  		t.Errorf("unexpected: %v", v)
   326  	}
   327  }
   328  
   329  func TestBoom_PutWithIncomplete(t *testing.T) {
   330  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   331  	defer cleanUp()
   332  
   333  	type Data struct {
   334  		ID  int64  `datastore:"-" boom:"id"`
   335  		Str string ``
   336  	}
   337  
   338  	bm := FromClient(ctx, client)
   339  
   340  	obj := &Data{Str: "Str"}
   341  	key, err := bm.Put(obj)
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  
   346  	if v := key.Kind(); v != "Data" {
   347  		t.Errorf("unexpected: %v", v)
   348  	}
   349  	if v := key.ID(); v == 0 {
   350  		t.Errorf("unexpected: %v", v)
   351  	}
   352  	if v := obj.ID; v != key.ID() {
   353  		t.Errorf("unexpected: %v", v)
   354  	}
   355  
   356  	obj = &Data{}
   357  	err = client.Get(ctx, key, obj)
   358  	if err != nil {
   359  		t.Fatal(err)
   360  	}
   361  
   362  	if v := obj.Str; v != "Str" {
   363  		t.Errorf("unexpected: %v", v)
   364  	}
   365  }
   366  
   367  func TestBoom_Get(t *testing.T) {
   368  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   369  	defer cleanUp()
   370  
   371  	type Data struct {
   372  		ID  int64  `datastore:"-" boom:"id"`
   373  		Str string ``
   374  	}
   375  
   376  	bm := FromClient(ctx, client)
   377  
   378  	key := client.IDKey("Data", 111, nil)
   379  	_, err := client.Put(ctx, key, &Data{Str: "Str"})
   380  	if err != nil {
   381  		t.Fatal(err)
   382  	}
   383  
   384  	obj := &Data{ID: 111}
   385  	err = bm.Get(obj)
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  
   390  	if v := obj.Str; v != "Str" {
   391  		t.Errorf("unexpected: %v", v)
   392  	}
   393  }
   394  
   395  func TestBoom_DeleteByStruct(t *testing.T) {
   396  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   397  	defer cleanUp()
   398  
   399  	type Data struct {
   400  		ID  int64  `datastore:"-" boom:"id"`
   401  		Str string ``
   402  	}
   403  
   404  	bm := FromClient(ctx, client)
   405  
   406  	key := client.IDKey("Data", 111, nil)
   407  	_, err := client.Put(ctx, key, &Data{Str: "Str"})
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  
   412  	obj := &Data{ID: 111}
   413  	err = bm.Delete(obj)
   414  	if err != nil {
   415  		t.Fatal(err)
   416  	}
   417  
   418  	err = client.Get(ctx, key, &Data{})
   419  	if err != datastore.ErrNoSuchEntity {
   420  		t.Fatal(err)
   421  	}
   422  }
   423  
   424  func TestBoom_DeleteByKey(t *testing.T) {
   425  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   426  	defer cleanUp()
   427  
   428  	type Data struct {
   429  		ID  int64  `datastore:"-" boom:"id"`
   430  		Str string ``
   431  	}
   432  
   433  	bm := FromClient(ctx, client)
   434  
   435  	key := client.IDKey("Data", 111, nil)
   436  	_, err := client.Put(ctx, key, &Data{Str: "Str"})
   437  	if err != nil {
   438  		t.Fatal(err)
   439  	}
   440  
   441  	err = bm.Delete(key)
   442  	if err != nil {
   443  		t.Fatal(err)
   444  	}
   445  
   446  	err = client.Get(ctx, key, &Data{})
   447  	if err != datastore.ErrNoSuchEntity {
   448  		t.Fatal(err)
   449  	}
   450  }
   451  
   452  func TestBoom_Count(t *testing.T) {
   453  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   454  	defer cleanUp()
   455  
   456  	type Data struct {
   457  		ID  int64  `datastore:"-" boom:"id"`
   458  		Str string ``
   459  	}
   460  
   461  	bm := FromClient(ctx, client)
   462  
   463  	key := client.IDKey("Data", 111, nil)
   464  	_, err := client.Put(ctx, key, &Data{Str: "Str"})
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	q := bm.NewQuery(bm.Kind(&Data{}))
   470  	cnt, err := bm.Count(q)
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  
   475  	if v := cnt; v != 1 {
   476  		t.Errorf("unexpected: %v", v)
   477  	}
   478  }
   479  
   480  func TestBoom_GetAll(t *testing.T) {
   481  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   482  	defer cleanUp()
   483  
   484  	type Data struct {
   485  		ID int64 `datastore:"-" boom:"id"`
   486  	}
   487  
   488  	const size = 100
   489  
   490  	bm := FromClient(ctx, client)
   491  
   492  	var list []*Data
   493  	for i := 0; i < size; i++ {
   494  		list = append(list, &Data{})
   495  	}
   496  
   497  	_, err := bm.PutMulti(list)
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  
   502  	q := bm.NewQuery(bm.Kind(&Data{}))
   503  	{
   504  		list = make([]*Data, 0)
   505  		_, err = bm.GetAll(q, &list)
   506  		if err != nil {
   507  			t.Fatal(err)
   508  		}
   509  
   510  		if v := len(list); v != size {
   511  			t.Errorf("unexpected: %v", v)
   512  		}
   513  		for _, obj := range list {
   514  			if v := obj.ID; v == 0 {
   515  				t.Errorf("unexpected: %v", v)
   516  			}
   517  		}
   518  	}
   519  	{
   520  		keys, err := bm.GetAll(q.KeysOnly(), nil)
   521  		if err != nil {
   522  			t.Fatal(err)
   523  		}
   524  		if v := len(keys); v != size {
   525  			t.Errorf("unexpected: %v", v)
   526  		}
   527  	}
   528  }
   529  
   530  func TestBoom_TagID(t *testing.T) {
   531  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   532  	defer cleanUp()
   533  
   534  	ctx = context.WithValue(ctx, contextClient{}, client)
   535  
   536  	bm := FromClient(ctx, client)
   537  
   538  	{ // ID(IntID)
   539  		type Data struct {
   540  			ID int64 `datastore:"-" boom:"id"`
   541  		}
   542  
   543  		key, err := bm.Put(&Data{ID: 1})
   544  		if err != nil {
   545  			t.Fatal(err)
   546  		}
   547  
   548  		if v := key.Kind(); v != "Data" {
   549  			t.Errorf("unexpected: %v", v)
   550  		}
   551  		if v := key.ID(); v != 1 {
   552  			t.Errorf("unexpected: %v", v)
   553  		}
   554  
   555  		err = bm.Get(&Data{1})
   556  		if err != nil {
   557  			t.Fatal(err)
   558  		}
   559  	}
   560  	{ // Name(StringID)
   561  		type Data struct {
   562  			ID string `datastore:"-" boom:"id"`
   563  		}
   564  
   565  		key, err := bm.Put(&Data{ID: "a"})
   566  		if err != nil {
   567  			t.Fatal(err)
   568  		}
   569  		if v := key.Kind(); v != "Data" {
   570  			t.Errorf("unexpected: %v", v)
   571  		}
   572  		if v := key.Name(); v != "a" {
   573  			t.Errorf("unexpected: %v", v)
   574  		}
   575  
   576  		err = bm.Get(&Data{"a"})
   577  		if err != nil {
   578  			t.Fatal(err)
   579  		}
   580  	}
   581  }
   582  
   583  func TestBoom_TagIDWithPropertyTranslator(t *testing.T) {
   584  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   585  	defer cleanUp()
   586  
   587  	ctx = context.WithValue(ctx, contextClient{}, client)
   588  
   589  	bm := FromClient(ctx, client)
   590  
   591  	{ // Put & Get with boom:"id"
   592  		type Data struct {
   593  			ID DataID `datastore:"-" boom:"id"`
   594  		}
   595  
   596  		key, err := bm.Put(&Data{ID: DataID(100)})
   597  		if err != nil {
   598  			t.Fatal(err)
   599  		}
   600  
   601  		if v := key.Kind(); v != "Data" {
   602  			t.Errorf("unexpected: %v", v)
   603  		}
   604  		if v := key.ID(); v != 100 {
   605  			t.Errorf("unexpected: %v", v)
   606  		}
   607  
   608  		err = bm.Get(&Data{ID: DataID(100)})
   609  		if err != nil {
   610  			t.Fatal(err)
   611  		}
   612  	}
   613  	{ // Put & Get	 with boom:"parent"
   614  		type Data struct {
   615  			ParentUserID UserID `datastore:"-" boom:"parent"`
   616  			ID           DataID `datastore:"-" boom:"id"`
   617  		}
   618  
   619  		key, err := bm.Put(&Data{ParentUserID: UserID(20), ID: DataID(100)})
   620  		if err != nil {
   621  			t.Fatal(err)
   622  		}
   623  
   624  		if v := key.Kind(); v != "Data" {
   625  			t.Errorf("unexpected: %v", v)
   626  		}
   627  		if v := key.ID(); v != 100 {
   628  			t.Errorf("unexpected: %v", v)
   629  		}
   630  		if v := key.ParentKey().Kind(); v != "User" {
   631  			t.Errorf("unexpected: %v", v)
   632  		}
   633  		if v := key.ParentKey().ID(); v != 20 {
   634  			t.Errorf("unexpected: %v", v)
   635  		}
   636  
   637  		err = bm.Get(&Data{ParentUserID: UserID(20), ID: DataID(100)})
   638  		if err != nil {
   639  			t.Fatal(err)
   640  		}
   641  	}
   642  	{ // Put & Get	 with boom:"id" that has ParentKey
   643  		type Data struct {
   644  			ID WithAncestorID `datastore:"-" boom:"id"`
   645  		}
   646  
   647  		key, err := bm.Put(&Data{ID: "20-100"})
   648  		if err != nil {
   649  			t.Fatal(err)
   650  		}
   651  
   652  		if v := key.Kind(); v != "Data" {
   653  			t.Errorf("unexpected: %v", v)
   654  		}
   655  		if v := key.ID(); v != 100 {
   656  			t.Errorf("unexpected: %v", v)
   657  		}
   658  		if v := key.ParentKey().Kind(); v != "User" {
   659  			t.Errorf("unexpected: %v", v)
   660  		}
   661  		if v := key.ParentKey().ID(); v != 20 {
   662  			t.Errorf("unexpected: %v", v)
   663  		}
   664  
   665  		err = bm.Get(&Data{ID: "20-100"})
   666  		if err != nil {
   667  			t.Fatal(err)
   668  		}
   669  	}
   670  }
   671  
   672  func TestBoom_TagParent(t *testing.T) {
   673  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   674  	defer cleanUp()
   675  
   676  	ctx = context.WithValue(ctx, contextClient{}, client)
   677  
   678  	bm := FromClient(ctx, client)
   679  
   680  	type Data struct {
   681  		ParentKey datastore.Key `datastore:"-" boom:"parent"`
   682  		ID        int64         `datastore:"-" boom:"id"`
   683  	}
   684  
   685  	parentKey := client.NameKey("Parent", "a", nil)
   686  	key, err := bm.Put(&Data{ParentKey: parentKey, ID: 1})
   687  	if err != nil {
   688  		t.Fatal(err)
   689  	}
   690  
   691  	if v := key.Kind(); v != "Data" {
   692  		t.Errorf("unexpected: %v", v)
   693  	}
   694  	if v := key.ID(); v != 1 {
   695  		t.Errorf("unexpected: %v", v)
   696  	}
   697  	if v := key.ParentKey().Kind(); v != "Parent" {
   698  		t.Errorf("unexpected: %v", v)
   699  	}
   700  	if v := key.ParentKey().Name(); v != "a" {
   701  		t.Errorf("unexpected: %v", v)
   702  	}
   703  
   704  	err = bm.Get(&Data{ParentKey: parentKey, ID: 1})
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  }
   709  
   710  func TestBoom_TagParentWithNilParent(t *testing.T) {
   711  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   712  	defer cleanUp()
   713  
   714  	ctx = context.WithValue(ctx, contextClient{}, client)
   715  
   716  	bm := FromClient(ctx, client)
   717  
   718  	type Data struct {
   719  		ParentKey datastore.Key `datastore:"-" boom:"parent"`
   720  		ID        int64         `datastore:"-" boom:"id"`
   721  	}
   722  
   723  	key, err := bm.Put(&Data{ParentKey: nil, ID: 1})
   724  	if err != nil {
   725  		t.Fatal(err)
   726  	}
   727  
   728  	if v := key.ParentKey(); v != nil {
   729  		t.Errorf("unexpected: %v", v)
   730  	}
   731  
   732  	err = bm.Get(&Data{ID: 1})
   733  	if err != nil {
   734  		t.Fatal(err)
   735  	}
   736  }
   737  
   738  func TestBoom_TagKind(t *testing.T) {
   739  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   740  	defer cleanUp()
   741  
   742  	bm := FromClient(ctx, client)
   743  
   744  	{
   745  		type Data struct {
   746  			Kind string `datastore:"-" boom:"kind,foo"`
   747  			ID   int64  `datastore:"-" boom:"id"`
   748  		}
   749  
   750  		{
   751  			obj := &Data{}
   752  
   753  			if v := bm.Kind(obj); v != "foo" {
   754  				t.Errorf("unexpected: %v", v)
   755  			}
   756  
   757  			key, err := bm.Put(obj)
   758  			if err != nil {
   759  				t.Fatal(err)
   760  			}
   761  			if v := key.Kind(); v != "foo" {
   762  				t.Errorf("unexpected: %v", v)
   763  			}
   764  
   765  			err = bm.Get(obj)
   766  			if err != nil {
   767  				t.Fatal(err)
   768  			}
   769  			if v := bm.Kind(obj); v != "foo" {
   770  				t.Errorf("unexpected: %v", v)
   771  			}
   772  		}
   773  		{
   774  			obj := &Data{Kind: "BAR"}
   775  
   776  			if v := bm.Kind(obj); v != "BAR" {
   777  				t.Errorf("unexpected: %v", v)
   778  			}
   779  
   780  			key, err := bm.Put(obj)
   781  			if err != nil {
   782  				t.Fatal(err)
   783  			}
   784  			if v := key.Kind(); v != "BAR" {
   785  				t.Errorf("unexpected: %v", v)
   786  			}
   787  
   788  			err = bm.Get(obj)
   789  			if err != nil {
   790  				t.Fatal(err)
   791  			}
   792  			if v := bm.Kind(obj); v != "BAR" {
   793  				t.Errorf("unexpected: %v", v)
   794  			}
   795  		}
   796  	}
   797  	{
   798  		type Data struct {
   799  			Kind string `datastore:"-" boom:"kind"`
   800  			ID   int64  `datastore:"-" boom:"id"`
   801  		}
   802  
   803  		{
   804  			obj := &Data{}
   805  
   806  			if v := bm.Kind(obj); v != "Data" {
   807  				t.Errorf("unexpected: %v", v)
   808  			}
   809  
   810  			key, err := bm.Put(obj)
   811  			if err != nil {
   812  				t.Fatal(err)
   813  			}
   814  			if v := key.Kind(); v != "Data" {
   815  				t.Errorf("unexpected: %v", v)
   816  			}
   817  
   818  			err = bm.Get(obj)
   819  			if err != nil {
   820  				t.Fatal(err)
   821  			}
   822  			if v := bm.Kind(obj); v != "Data" {
   823  				t.Errorf("unexpected: %v", v)
   824  			}
   825  		}
   826  		{
   827  			obj := &Data{Kind: "BAR"}
   828  
   829  			if v := bm.Kind(obj); v != "BAR" {
   830  				t.Errorf("unexpected: %v", v)
   831  			}
   832  
   833  			key, err := bm.Put(obj)
   834  			if err != nil {
   835  				t.Fatal(err)
   836  			}
   837  			if v := key.Kind(); v != "BAR" {
   838  				t.Errorf("unexpected: %v", v)
   839  			}
   840  
   841  			err = bm.Get(obj)
   842  			if err != nil {
   843  				t.Fatal(err)
   844  			}
   845  			if v := bm.Kind(obj); v != "BAR" {
   846  				t.Errorf("unexpected: %v", v)
   847  			}
   848  		}
   849  	}
   850  }
   851  
   852  var _ datastore.PropertyTranslator = CustomID(0)
   853  
   854  type CustomID int64
   855  type contextBoom struct{}
   856  
   857  type HasCustomID struct {
   858  	ID   CustomID `datastore:"-" boom:"id"`
   859  	Name string
   860  }
   861  
   862  func (id CustomID) ToPropertyValue(ctx context.Context) (interface{}, error) {
   863  	bm, ok := ctx.Value(contextBoom{}).(*Boom)
   864  	if !ok {
   865  		return nil, fmt.Errorf("unexpected type: %T", ctx.Value("boom"))
   866  	}
   867  	key := bm.Client.IDKey(bm.Kind(&HasCustomID{}), int64(id), nil)
   868  	return key, nil
   869  }
   870  
   871  func (CustomID) FromPropertyValue(ctx context.Context, p datastore.Property) (interface{}, error) {
   872  	bm, ok := ctx.Value(contextBoom{}).(*Boom)
   873  	if !ok {
   874  		return nil, fmt.Errorf("unexpected type: %T", ctx.Value("boom"))
   875  	}
   876  	key, ok := p.Value.(datastore.Key)
   877  	if !ok {
   878  		return nil, fmt.Errorf("unexpected type: %T", p.Value)
   879  	}
   880  	if v := bm.Kind(&HasCustomID{}); v != key.Kind() {
   881  		return nil, fmt.Errorf("unexpected kind: %s", v)
   882  	}
   883  
   884  	return CustomID(key.ID()), nil
   885  }
   886  
   887  func TestBoom_KindWithPropertyTranslator(t *testing.T) {
   888  	ctx, client, cleanUp := testutils.SetupCloudDatastore(t)
   889  	defer cleanUp()
   890  
   891  	bm := FromClient(ctx, client)
   892  	ctx = context.WithValue(ctx, contextBoom{}, bm)
   893  	bm.Context = ctx
   894  
   895  	obj := &HasCustomID{
   896  		Name: "foobar",
   897  	}
   898  	key, err := bm.Put(obj)
   899  	if err != nil {
   900  		t.Fatal(err)
   901  	}
   902  
   903  	obj = &HasCustomID{ID: CustomID(key.ID())}
   904  	err = bm.Get(obj)
   905  	if err != nil {
   906  		t.Fatal(err)
   907  	}
   908  
   909  	if v := obj.Name; v != "foobar" {
   910  		t.Errorf("unexpected: %v", v)
   911  	}
   912  }