go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/key_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datastore
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"testing"
    21  
    22  	. "github.com/smartystreets/goconvey/convey"
    23  	. "go.chromium.org/luci/common/testing/assertions"
    24  )
    25  
    26  func ShouldEqualKey(actual any, expected ...any) string {
    27  	if len(expected) != 1 {
    28  		return fmt.Sprintf("Assertion requires 1 expected value, got %d", len(expected))
    29  	}
    30  	if actual.(*Key).Equal(expected[0].(*Key)) {
    31  		return ""
    32  	}
    33  	return fmt.Sprintf("Expected: %q\nActual: %q", actual, expected[0])
    34  }
    35  
    36  func TestKeyEncode(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	kc := MkKeyContext("appid", "ns")
    40  	keys := []*Key{
    41  		kc.MakeKey("kind", 1),
    42  		kc.MakeKey("nerd", "moo"),
    43  		kc.MakeKey("parent", 10, "renerd", "moo"),
    44  	}
    45  
    46  	Convey("Key Round trip", t, func() {
    47  		for _, k := range keys {
    48  			k := k
    49  			Convey(k.String(), func() {
    50  				enc := k.Encode()
    51  				dec, err := NewKeyEncoded(enc)
    52  				So(err, ShouldBeNil)
    53  				So(dec, ShouldNotBeNil)
    54  				So(dec, ShouldEqualKey, k)
    55  
    56  				dec2, err := NewKeyEncoded(enc)
    57  				So(err, ShouldBeNil)
    58  				So(dec2, ShouldEqualKey, dec)
    59  				So(dec2, ShouldEqualKey, k)
    60  			})
    61  
    62  			Convey(k.String()+" (json)", func() {
    63  				data, err := k.MarshalJSON()
    64  				So(err, ShouldBeNil)
    65  
    66  				dec := &Key{}
    67  				So(dec.UnmarshalJSON(data), ShouldBeNil)
    68  				So(dec, ShouldEqualKey, k)
    69  			})
    70  		}
    71  	})
    72  
    73  	Convey("NewKey", t, func() {
    74  		Convey("single", func() {
    75  			k := MkKeyContext("appid", "ns").NewKey("kind", "", 1, nil)
    76  			So(k, ShouldEqualKey, keys[0])
    77  		})
    78  
    79  		Convey("empty", func() {
    80  			So(MkKeyContext("appid", "ns").NewKeyToks(nil), ShouldBeNil)
    81  		})
    82  
    83  		Convey("nest", func() {
    84  			kc := MkKeyContext("appid", "ns")
    85  			k := kc.NewKey("renerd", "moo", 0, kc.NewKey("parent", "", 10, nil))
    86  			So(k, ShouldEqualKey, keys[2])
    87  		})
    88  	})
    89  
    90  	Convey("Key bad encoding", t, func() {
    91  		Convey("extra junk before", func() {
    92  			enc := keys[2].Encode()
    93  			_, err := NewKeyEncoded("/" + enc)
    94  			So(err, ShouldErrLike, "illegal base64")
    95  		})
    96  
    97  		Convey("extra junk after", func() {
    98  			enc := keys[2].Encode()
    99  			_, err := NewKeyEncoded(enc[:len(enc)-1])
   100  			So(err, ShouldNotBeNil)
   101  		})
   102  
   103  		Convey("json encoding includes quotes", func() {
   104  			data, err := keys[0].MarshalJSON()
   105  			So(err, ShouldBeNil)
   106  
   107  			dec := &Key{}
   108  			err = dec.UnmarshalJSON(append(data, '!'))
   109  			So(err, ShouldErrLike, "bad JSON key")
   110  		})
   111  	})
   112  }
   113  
   114  func TestKeyValidity(t *testing.T) {
   115  	t.Parallel()
   116  
   117  	Convey("keys validity", t, func() {
   118  		kc := MkKeyContext("aid", "ns")
   119  
   120  		Convey("incomplete", func() {
   121  			So(kc.MakeKey("kind", 1).IsIncomplete(), ShouldBeFalse)
   122  			So(kc.MakeKey("kind", 0).IsIncomplete(), ShouldBeTrue)
   123  		})
   124  
   125  		Convey("invalid", func() {
   126  			So(kc.MakeKey("hat", "face", "__kind__", 1).Valid(true, kc), ShouldBeTrue)
   127  
   128  			bads := []*Key{
   129  				MkKeyContext("aid", "ns").NewKeyToks([]KeyTok{{"Kind", 1, "1"}}),
   130  				MkKeyContext("", "ns").MakeKey("", "ns", "hat", "face"),
   131  				kc.MakeKey("base", 1, "", "id"),
   132  				kc.MakeKey("hat", "face", "__kind__", 1),
   133  				kc.MakeKey("hat", 0, "kind", 1),
   134  			}
   135  			for _, k := range bads {
   136  				Convey(k.String(), func() {
   137  					So(k.Valid(false, kc), ShouldBeFalse)
   138  				})
   139  			}
   140  		})
   141  
   142  		Convey("partially valid", func() {
   143  			So(kc.MakeKey("kind", "").PartialValid(kc), ShouldBeTrue)
   144  			So(kc.MakeKey("kind", "", "child", "").PartialValid(kc), ShouldBeFalse)
   145  		})
   146  	})
   147  }
   148  
   149  func TestMiscKey(t *testing.T) {
   150  	t.Parallel()
   151  
   152  	Convey("KeyRoot", t, func() {
   153  		kc := MkKeyContext("appid", "ns")
   154  
   155  		k := kc.MakeKey("parent", 10, "renerd", "moo")
   156  		r := kc.MakeKey("parent", 10)
   157  		So(k.Root(), ShouldEqualKey, r)
   158  	})
   159  
   160  	Convey("KeysEqual", t, func() {
   161  		kc := MkKeyContext("a", "n")
   162  
   163  		k1 := kc.MakeKey("knd", 1)
   164  		k2 := kc.MakeKey("knd", 1)
   165  		So(k1.Equal(k2), ShouldBeTrue)
   166  		k3 := kc.MakeKey("knd", 2)
   167  		So(k1.Equal(k3), ShouldBeFalse)
   168  	})
   169  
   170  	Convey("KeyString", t, func() {
   171  		kc := MkKeyContext("a", "n")
   172  
   173  		k1 := kc.MakeKey("knd", 1, "other", "wat")
   174  		So(k1.String(), ShouldEqual, "a:n:/knd,1/other,\"wat\"")
   175  	})
   176  
   177  	Convey("HasAncestor", t, func() {
   178  		kc := MkKeyContext("a", "n")
   179  
   180  		k1 := kc.MakeKey("kind", 1)
   181  		k2 := kc.MakeKey("kind", 1, "other", "wat")
   182  		k3 := kc.MakeKey("kind", 1, "other", "wat", "extra", "data")
   183  		k4 := MkKeyContext("something", "n").MakeKey("kind", 1)
   184  		k5 := kc.MakeKey("kind", 1, "other", "meep")
   185  
   186  		So(k1.HasAncestor(k1), ShouldBeTrue)
   187  		So(k1.HasAncestor(k2), ShouldBeFalse)
   188  		So(k2.HasAncestor(k5), ShouldBeFalse)
   189  		So(k5.HasAncestor(k2), ShouldBeFalse)
   190  		So(k2.HasAncestor(k1), ShouldBeTrue)
   191  		So(k3.HasAncestor(k2), ShouldBeTrue)
   192  		So(k3.HasAncestor(k1), ShouldBeTrue)
   193  		So(k3.HasAncestor(k4), ShouldBeFalse)
   194  	})
   195  
   196  	Convey("*GenericKey supports json encoding", t, func() {
   197  		type TestStruct struct {
   198  			Key *Key
   199  		}
   200  		t := &TestStruct{
   201  			MkKeyContext("aid", "ns").NewKey("kind", "id", 0,
   202  				MkKeyContext("aid", "ns").NewKey("parent", "", 1, nil),
   203  			)}
   204  		d, err := json.Marshal(t)
   205  		So(err, ShouldBeNil)
   206  		t2 := &TestStruct{}
   207  		err = json.Unmarshal(d, t2)
   208  		So(err, ShouldBeNil)
   209  		So(t.Key, ShouldEqualKey, t2.Key)
   210  	})
   211  }
   212  
   213  func shouldBeLess(actual any, expected ...any) string {
   214  	a, b := actual.(*Key), expected[0].(*Key)
   215  	if a.Less(b) {
   216  		return ""
   217  	}
   218  	return fmt.Sprintf("Expected %q < %q", a.String(), b.String())
   219  }
   220  
   221  func shouldNotBeEqual(actual any, expected ...any) string {
   222  	a, b := actual.(*Key), expected[0].(*Key)
   223  	if !a.Equal(b) {
   224  		return ""
   225  	}
   226  	return fmt.Sprintf("Expected %q != %q", a.String(), b.String())
   227  }
   228  
   229  func shouldNotBeLess(actual any, expected ...any) string {
   230  	a, b := actual.(*Key), expected[0].(*Key)
   231  	if !a.Less(b) {
   232  		return ""
   233  	}
   234  	return fmt.Sprintf("Expected !(%q < %q)", a.String(), b.String())
   235  }
   236  
   237  func TestKeySort(t *testing.T) {
   238  	t.Parallel()
   239  
   240  	Convey("KeyTok.Less() works", t, func() {
   241  		So((KeyTok{"a", 0, "1"}).Less(KeyTok{"b", 0, "2"}), ShouldBeTrue)
   242  		So((KeyTok{"b", 0, "1"}).Less(KeyTok{"a", 0, "2"}), ShouldBeFalse)
   243  		So((KeyTok{"kind", 0, "1"}).Less(KeyTok{"kind", 0, "2"}), ShouldBeTrue)
   244  		So((KeyTok{"kind", 1, ""}).Less(KeyTok{"kind", 2, ""}), ShouldBeTrue)
   245  		So((KeyTok{"kind", 1, ""}).Less(KeyTok{"kind", 0, "1"}), ShouldBeTrue)
   246  	})
   247  
   248  	Convey("Key comparison works", t, func() {
   249  		s := []*Key{
   250  			MkKeyContext("A", "").MakeKey("kind", 1),
   251  			MkKeyContext("A", "n").MakeKey("kind", 1),
   252  			MkKeyContext("A", "n").MakeKey("kind", 1, "something", "else"),
   253  			MkKeyContext("A", "n").MakeKey("kind", "1"),
   254  			MkKeyContext("A", "n").MakeKey("kind", "1", "something", "else"),
   255  			MkKeyContext("A", "n").MakeKey("other", 1, "something", "else"),
   256  			MkKeyContext("a", "").MakeKey("kind", 1),
   257  			MkKeyContext("a", "n").MakeKey("kind", 1),
   258  			MkKeyContext("a", "n").MakeKey("kind", 2),
   259  			MkKeyContext("a", "p").MakeKey("aleph", 1),
   260  			MkKeyContext("b", "n").MakeKey("kind", 2),
   261  		}
   262  
   263  		for i := 1; i < len(s); i++ {
   264  			So(s[i-1], shouldBeLess, s[i])
   265  			So(s[i-1], shouldNotBeEqual, s[i])
   266  			So(s[i], shouldNotBeEqual, s[i-1])
   267  			So(s[i], shouldNotBeLess, s[i-1])
   268  		}
   269  	})
   270  }