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 }