fork of indigo with slightly nicer lexgen
at main 14 kB view raw
1// This file contains tests which are the same across language implementations. 2// AKA, if you update this file, you should probably update the corresponding 3// file in atproto repo (typescript) 4package mst 5 6import ( 7 "context" 8 "fmt" 9 "testing" 10 11 "github.com/bluesky-social/indigo/util" 12 "github.com/stretchr/testify/assert" 13 14 "github.com/ipfs/go-cid" 15) 16 17func TestLeadingZeros(t *testing.T) { 18 msg := "MST 'depth' computation (SHA-256 leading zeros)" 19 assert.Equal(t, leadingZerosOnHash(""), 0, msg) 20 assert.Equal(t, leadingZerosOnHash("asdf"), 0, msg) 21 assert.Equal(t, leadingZerosOnHash("blue"), 1, msg) 22 assert.Equal(t, leadingZerosOnHash("2653ae71"), 0, msg) 23 assert.Equal(t, leadingZerosOnHash("88bfafc7"), 2, msg) 24 assert.Equal(t, leadingZerosOnHash("2a92d355"), 4, msg) 25 assert.Equal(t, leadingZerosOnHash("884976f5"), 6, msg) 26 assert.Equal(t, leadingZerosOnHash("app.bsky.feed.post/454397e440ec"), 4, msg) 27 assert.Equal(t, leadingZerosOnHash("app.bsky.feed.post/9adeb165882c"), 8, msg) 28} 29 30func TestPrefixLen(t *testing.T) { 31 msg := "length of common prefix between strings" 32 assert.Equal(t, countPrefixLen("abc", "abc"), 3, msg) 33 assert.Equal(t, countPrefixLen("", "abc"), 0, msg) 34 assert.Equal(t, countPrefixLen("abc", ""), 0, msg) 35 assert.Equal(t, countPrefixLen("ab", "abc"), 2, msg) 36 assert.Equal(t, countPrefixLen("abc", "ab"), 2, msg) 37 assert.Equal(t, countPrefixLen("abcde", "abc"), 3, msg) 38 assert.Equal(t, countPrefixLen("abc", "abcde"), 3, msg) 39 assert.Equal(t, countPrefixLen("abcde", "abc1"), 3, msg) 40 assert.Equal(t, countPrefixLen("abcde", "abb"), 2, msg) 41 assert.Equal(t, countPrefixLen("abcde", "qbb"), 0, msg) 42 assert.Equal(t, countPrefixLen("abc", "abc\x00"), 3, msg) 43 assert.Equal(t, countPrefixLen("abc\x00", "abc"), 3, msg) 44} 45 46func TestPrefixLenWide(t *testing.T) { 47 // NOTE: these are not cross-language consistent! 48 msg := "length of common prefix between strings (wide chars)" 49 assert.Equal(t, len("jalapeño"), 9, msg) // 8 in javascript 50 assert.Equal(t, len("💩"), 4, msg) // 2 in javascript 51 assert.Equal(t, len("👩‍👧‍👧"), 18, msg) // 8 in javascript 52 53 // many of the below are different in JS 54 assert.Equal(t, countPrefixLen("jalapeño", "jalapeno"), 6, msg) 55 assert.Equal(t, countPrefixLen("jalapeñoA", "jalapeñoB"), 9, msg) 56 assert.Equal(t, countPrefixLen("coöperative", "coüperative"), 3, msg) 57 assert.Equal(t, countPrefixLen("abc💩abc", "abcabc"), 3, msg) 58 assert.Equal(t, countPrefixLen("💩abc", "💩ab"), 6, msg) 59 assert.Equal(t, countPrefixLen("abc👩‍👦‍👦de", "abc👩‍👧‍👧de"), 13, msg) 60} 61 62func mapToCidMapDecode(t *testing.T, a map[string]string) map[string]cid.Cid { 63 out := make(map[string]cid.Cid) 64 for k, v := range a { 65 c, err := cid.Decode(v) 66 if err != nil { 67 t.Fatal(err) 68 } 69 out[k] = c 70 } 71 return out 72} 73 74func mapToMstRootCidString(t *testing.T, m map[string]string) string { 75 bs := memBs() 76 ctx := context.Background() 77 mst := cidMapToMst(t, bs, mapToCidMapDecode(t, m)) 78 ncid, err := mst.GetPointer(ctx) 79 if err != nil { 80 t.Fatal(err) 81 } 82 return ncid.String() 83} 84 85func TestAllowedKeys(t *testing.T) { 86 87 bs := memBs() 88 ctx := context.Background() 89 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 90 cid1, err := cid.Decode(cid1str) 91 if err != nil { 92 t.Fatal(err) 93 } 94 95 empty := map[string]string{} 96 mst := cidMapToMst(t, bs, mapToCidMapDecode(t, empty)) 97 98 // rejects empty key 99 _, err = mst.Add(ctx, "", cid1, -1) 100 assert.NotNil(t, err) 101 102 // rejects a key with no collection 103 _, err = mst.Add(ctx, "asdf", cid1, -1) 104 assert.NotNil(t, err) 105 106 // rejects a key with a nested collection 107 _, err = mst.Add(ctx, "nested/collection/asdf", cid1, -1) 108 assert.NotNil(t, err) 109 110 // rejects on empty coll or rkey 111 _, err = mst.Add(ctx, "coll/", cid1, -1) 112 assert.NotNil(t, err) 113 _, err = mst.Add(ctx, "/rkey", cid1, -1) 114 assert.NotNil(t, err) 115 116 // rejects non-ascii chars 117 _, err = mst.Add(ctx, "coll/jalapeñoA", cid1, -1) 118 assert.NotNil(t, err) 119 _, err = mst.Add(ctx, "coll/coöperative", cid1, -1) 120 assert.NotNil(t, err) 121 _, err = mst.Add(ctx, "coll/abc💩", cid1, -1) 122 assert.NotNil(t, err) 123 124 // rejects ascii that we dont support 125 _, err = mst.Add(ctx, "coll/key$", cid1, -1) 126 assert.NotNil(t, err) 127 _, err = mst.Add(ctx, "coll/key%", cid1, -1) 128 assert.NotNil(t, err) 129 _, err = mst.Add(ctx, "coll/key(", cid1, -1) 130 assert.NotNil(t, err) 131 _, err = mst.Add(ctx, "coll/key)", cid1, -1) 132 assert.NotNil(t, err) 133 _, err = mst.Add(ctx, "coll/key+", cid1, -1) 134 assert.NotNil(t, err) 135 _, err = mst.Add(ctx, "coll/key=", cid1, -1) 136 assert.NotNil(t, err) 137 138 // rejects keys over 256 chars 139 _, err = mst.Add(ctx, "coll/asdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisdufpao", cid1, -1) 140 assert.NotNil(t, err) 141 142 // allows long key under 256 chars 143 _, err = mst.Add(ctx, "coll/asdofiupoiwqeurfpaosidfuapsodirupasoirupasoeiruaspeoriuaspeoriu2p3o4iu1pqw3oiuaspdfoiuaspdfoiuasdfpoiasdufpwoieruapsdofiuaspdfoiuasdpfoiausdfpoasidfupasodifuaspdofiuasdpfoiasudfpoasidfuapsodfiuasdpfoiausdfpoasidufpasodifuapsdofiuasdpofiuasdfpoaisduf", cid1, -1) 144 assert.Nil(t, err) 145 146 // allows URL-safe chars 147 _, err = mst.Add(ctx, "coll/key0", cid1, -1) 148 assert.Nil(t, err) 149 _, err = mst.Add(ctx, "coll/key_", cid1, -1) 150 assert.Nil(t, err) 151 _, err = mst.Add(ctx, "coll/key:", cid1, -1) 152 assert.Nil(t, err) 153 _, err = mst.Add(ctx, "coll/key.", cid1, -1) 154 assert.Nil(t, err) 155 _, err = mst.Add(ctx, "coll/key-", cid1, -1) 156 assert.Nil(t, err) 157 158} 159 160func TestManualNode(t *testing.T) { 161 162 bs := memBs() 163 cst := util.CborStore(bs) 164 cid1, err := cid.Decode("bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454") 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 simple_nd := NodeData{ 170 Left: nil, 171 Entries: []TreeEntry{ 172 { 173 PrefixLen: 0, 174 KeySuffix: []byte("com.example.record/3jqfcqzm3fo2j"), 175 Val: cid1, 176 Tree: nil, 177 }, 178 }, 179 } 180 mcid, err := cst.Put(context.TODO(), &simple_nd) 181 if err != nil { 182 t.Fatal(err) 183 } 184 block, err := bs.Get(context.TODO(), mcid) 185 if err != nil { 186 t.Fatal(err) 187 } 188 if false { 189 fmt.Printf("%#v\n", block) 190 } 191 assert.Equal(t, mcid.String(), "bafyreibj4lsc3aqnrvphp5xmrnfoorvru4wynt6lwidqbm2623a6tatzdu") 192 193} 194 195func TestInteropKnownMaps(t *testing.T) { 196 197 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 198 199 // empty map 200 emptyMap := map[string]string{} 201 assert.Equal(t, mapToMstRootCidString(t, emptyMap), "bafyreie5737gdxlw5i64vzichcalba3z2v5n6icifvx5xytvske7mr3hpm") 202 203 // no depth, single entry 204 trivialMap := map[string]string{ 205 "com.example.record/3jqfcqzm3fo2j": cid1str, 206 } 207 assert.Equal(t, mapToMstRootCidString(t, trivialMap), "bafyreibj4lsc3aqnrvphp5xmrnfoorvru4wynt6lwidqbm2623a6tatzdu") 208 209 // single layer=2 entry 210 singlelayer2Map := map[string]string{ 211 "com.example.record/3jqfcqzm3fx2j": cid1str, 212 } 213 assert.Equal(t, mapToMstRootCidString(t, singlelayer2Map), "bafyreih7wfei65pxzhauoibu3ls7jgmkju4bspy4t2ha2qdjnzqvoy33ai") 214 215 // pretty simple, but with some depth 216 simpleMap := map[string]string{ 217 "com.example.record/3jqfcqzm3fp2j": cid1str, 218 "com.example.record/3jqfcqzm3fr2j": cid1str, 219 "com.example.record/3jqfcqzm3fs2j": cid1str, 220 "com.example.record/3jqfcqzm3ft2j": cid1str, 221 "com.example.record/3jqfcqzm4fc2j": cid1str, 222 } 223 assert.Equal(t, mapToMstRootCidString(t, simpleMap), "bafyreicmahysq4n6wfuxo522m6dpiy7z7qzym3dzs756t5n7nfdgccwq7m") 224} 225 226func TestInteropKnownMapsTricky(t *testing.T) { 227 t.Skip("TODO: these are currently disallowed in typescript implementation") 228 229 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 230 231 // include several known edge cases 232 trickyMap := map[string]string{ 233 "": cid1str, 234 "jalapeño": cid1str, 235 "coöperative": cid1str, 236 "coüperative": cid1str, 237 "abc\x00": cid1str, 238 } 239 assert.Equal(t, mapToMstRootCidString(t, trickyMap), "bafyreiecb33zh7r2sc3k2wthm6exwzfktof63kmajeildktqc25xj6qzx4") 240} 241 242// "trims top of tree on delete" 243func TestInteropEdgeCasesTrimTop(t *testing.T) { 244 245 bs := memBs() 246 ctx := context.Background() 247 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 248 l1root := "bafyreifnqrwbk6ffmyaz5qtujqrzf5qmxf7cbxvgzktl4e3gabuxbtatv4" 249 l0root := "bafyreie4kjuxbwkhzg2i5dljaswcroeih4dgiqq6pazcmunwt2byd725vi" 250 251 trimMap := map[string]string{ 252 "com.example.record/3jqfcqzm3fn2j": cid1str, // level 0 253 "com.example.record/3jqfcqzm3fo2j": cid1str, // level 0 254 "com.example.record/3jqfcqzm3fp2j": cid1str, // level 0 255 "com.example.record/3jqfcqzm3fs2j": cid1str, // level 0 256 "com.example.record/3jqfcqzm3ft2j": cid1str, // level 0 257 "com.example.record/3jqfcqzm3fu2j": cid1str, // level 1 258 } 259 trimMst := cidMapToMst(t, bs, mapToCidMapDecode(t, trimMap)) 260 trimBefore, err := trimMst.GetPointer(ctx) 261 if err != nil { 262 t.Fatal(err) 263 } 264 assert.Equal(t, trimMst.layer, 1) 265 assert.Equal(t, trimBefore.String(), l1root) 266 267 trimMst, err = trimMst.Delete(ctx, "com.example.record/3jqfcqzm3fs2j") // level 1 268 if err != nil { 269 t.Fatal(err) 270 } 271 trimAfter, err := trimMst.GetPointer(ctx) 272 if err != nil { 273 t.Fatal(err) 274 } 275 fmt.Printf("%#v\n", trimMst) 276 assert.Equal(t, trimMst.layer, 0) 277 assert.Equal(t, trimAfter.String(), l0root) 278} 279 280func TestInteropEdgeCasesInsertion(t *testing.T) { 281 282 bs := memBs() 283 ctx := context.Background() 284 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 285 cid1, err := cid.Decode(cid1str) 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 // "handles insertion that splits two layers down" 291 l1root := "bafyreiettyludka6fpgp33stwxfuwhkzlur6chs4d2v4nkmq2j3ogpdjem" 292 l2root := "bafyreid2x5eqs4w4qxvc5jiwda4cien3gw2q6cshofxwnvv7iucrmfohpm" 293 insertionMap := map[string]string{ 294 "com.example.record/3jqfcqzm3fo2j": cid1str, // A; level 0 295 "com.example.record/3jqfcqzm3fp2j": cid1str, // B; level 0 296 "com.example.record/3jqfcqzm3fr2j": cid1str, // C; level 0 297 "com.example.record/3jqfcqzm3fs2j": cid1str, // D; level 1 298 "com.example.record/3jqfcqzm3ft2j": cid1str, // E; level 0 299 "com.example.record/3jqfcqzm3fz2j": cid1str, // G; level 0 300 "com.example.record/3jqfcqzm4fc2j": cid1str, // H; level 0 301 "com.example.record/3jqfcqzm4fd2j": cid1str, // I; level 1 302 "com.example.record/3jqfcqzm4ff2j": cid1str, // J; level 0 303 "com.example.record/3jqfcqzm4fg2j": cid1str, // K; level 0 304 "com.example.record/3jqfcqzm4fh2j": cid1str, // L; level 0 305 } 306 insertionMst := cidMapToMst(t, bs, mapToCidMapDecode(t, insertionMap)) 307 insertionBefore, err := insertionMst.GetPointer(ctx) 308 if err != nil { 309 t.Fatal(err) 310 } 311 assert.Equal(t, insertionMst.layer, 1) 312 assert.Equal(t, insertionBefore.String(), l1root) 313 314 // insert F, which will push E out of the node with G+H to a new node under D 315 insertionMst, err = insertionMst.Add(ctx, "com.example.record/3jqfcqzm3fx2j", cid1, -1) // F; level 2 316 if err != nil { 317 t.Fatal(err) 318 } 319 insertionAfter, err := insertionMst.GetPointer(ctx) 320 if err != nil { 321 t.Fatal(err) 322 } 323 assert.Equal(t, insertionMst.layer, 2) 324 assert.Equal(t, insertionAfter.String(), l2root) 325 326 // remove F, which should push E back over with G+H 327 insertionMst, err = insertionMst.Delete(ctx, "com.example.record/3jqfcqzm3fx2j") // F; level 2 328 if err != nil { 329 t.Fatal(err) 330 } 331 insertionFinal, err := insertionMst.GetPointer(ctx) 332 if err != nil { 333 t.Fatal(err) 334 } 335 assert.Equal(t, insertionMst.layer, 1) 336 assert.Equal(t, insertionFinal.String(), l1root) 337} 338 339// "handles new layers that are two higher than existing" 340func TestInteropEdgeCasesHigher(t *testing.T) { 341 342 bs := memBs() 343 ctx := context.Background() 344 cid1str := "bafyreie5cvv4h45feadgeuwhbcutmh6t2ceseocckahdoe6uat64zmz454" 345 cid1, err := cid.Decode(cid1str) 346 if err != nil { 347 t.Fatal(err) 348 } 349 350 l0root := "bafyreidfcktqnfmykz2ps3dbul35pepleq7kvv526g47xahuz3rqtptmky" 351 l2root := "bafyreiavxaxdz7o7rbvr3zg2liox2yww46t7g6hkehx4i4h3lwudly7dhy" 352 l2root2 := "bafyreig4jv3vuajbsybhyvb7gggvpwh2zszwfyttjrj6qwvcsp24h6popu" 353 higherMap := map[string]string{ 354 "com.example.record/3jqfcqzm3ft2j": cid1str, // A; level 0 355 "com.example.record/3jqfcqzm3fz2j": cid1str, // C; level 0 356 } 357 higherMst := cidMapToMst(t, bs, mapToCidMapDecode(t, higherMap)) 358 higherBefore, err := higherMst.GetPointer(ctx) 359 if err != nil { 360 t.Fatal(err) 361 } 362 assert.Equal(t, higherMst.layer, 0) 363 assert.Equal(t, higherBefore.String(), l0root) 364 365 // insert B, which is two levels above 366 higherMst, err = higherMst.Add(ctx, "com.example.record/3jqfcqzm3fx2j", cid1, -1) // B; level 2 367 if err != nil { 368 t.Fatal(err) 369 } 370 higherAfter, err := higherMst.GetPointer(ctx) 371 if err != nil { 372 t.Fatal(err) 373 } 374 assert.Equal(t, higherAfter.String(), l2root) 375 376 // remove B 377 higherMst, err = higherMst.Delete(ctx, "com.example.record/3jqfcqzm3fx2j") // B; level 2 378 if err != nil { 379 t.Fatal(err) 380 } 381 higherAgain, err := higherMst.GetPointer(ctx) 382 if err != nil { 383 t.Fatal(err) 384 } 385 assert.Equal(t, higherMst.layer, 0) 386 assert.Equal(t, higherAgain.String(), l0root) 387 388 // insert B (level=2) and D (level=1) 389 higherMst, err = higherMst.Add(ctx, "com.example.record/3jqfcqzm3fx2j", cid1, -1) // B; level 2 390 if err != nil { 391 t.Fatal(err) 392 } 393 higherMst, err = higherMst.Add(ctx, "com.example.record/3jqfcqzm4fd2j", cid1, -1) // D; level 1 394 if err != nil { 395 t.Fatal(err) 396 } 397 higherYetAgain, err := higherMst.GetPointer(ctx) 398 if err != nil { 399 t.Fatal(err) 400 } 401 assert.Equal(t, higherMst.layer, 2) 402 assert.Equal(t, higherYetAgain.String(), l2root2) 403 404 // remove D 405 higherMst, err = higherMst.Delete(ctx, "com.example.record/3jqfcqzm4fd2j") // D; level 1 406 if err != nil { 407 t.Fatal(err) 408 } 409 higherFinal, err := higherMst.GetPointer(ctx) 410 if err != nil { 411 t.Fatal(err) 412 } 413 assert.Equal(t, higherMst.layer, 2) 414 assert.Equal(t, higherFinal.String(), l2root) 415}