github.com/holochain/holochain-proto@v0.1.0-alpha-26.0.20200915073418-5c83169c9b5b/chain_test.go (about) 1 package holochain 2 3 import ( 4 "bytes" 5 "fmt" 6 "path/filepath" 7 "reflect" 8 "regexp" 9 "strings" 10 "testing" 11 "time" 12 13 . "github.com/holochain/holochain-proto/hash" 14 . "github.com/smartystreets/goconvey/convey" 15 ) 16 17 func TestChainNew(t *testing.T) { 18 hashSpec, _, _ := chainTestSetup() 19 Convey("it should make an empty chain", t, func() { 20 c := NewChain(hashSpec) 21 So(len(c.Headers), ShouldEqual, 0) 22 So(len(c.Entries), ShouldEqual, 0) 23 }) 24 25 } 26 27 func TestChainNewChainFromFile(t *testing.T) { 28 d := SetupTestDir() 29 defer CleanupTestDir(d) 30 hashSpec, key, now := chainTestSetup() 31 32 var c *Chain 33 var err error 34 path := filepath.Join(d, "chain.dat") 35 Convey("it should make an empty chain with encoder", t, func() { 36 c, err = NewChainFromFile(hashSpec, path) 37 So(err, ShouldBeNil) 38 So(c.s, ShouldNotBeNil) 39 So(FileExists(path), ShouldBeTrue) 40 }) 41 42 e := GobEntry{C: "some data1"} 43 c.AddEntry(now, "entryTypeFoo1", &e, key) 44 e = GobEntry{C: "some other data2"} 45 c.AddEntry(now, "entryTypeFoo2", &e, key) 46 dump := c.String() 47 c.s.Close() 48 c, err = NewChainFromFile(hashSpec, path) 49 Convey("it should load chain data if available", t, func() { 50 So(err, ShouldBeNil) 51 So(c.String(), ShouldEqual, dump) 52 }) 53 54 e = GobEntry{C: "yet other data"} 55 c.AddEntry(now, "yourData", &e, key) 56 dump = c.String() 57 c.s.Close() 58 59 c, err = NewChainFromFile(hashSpec, path) 60 Convey("should continue to append data after reload", t, func() { 61 So(err, ShouldBeNil) 62 So(c.String(), ShouldEqual, dump) 63 }) 64 } 65 66 func TestChainTop(t *testing.T) { 67 hashSpec, key, now := chainTestSetup() 68 c := NewChain(hashSpec) 69 var hash *Hash 70 var hd *Header 71 Convey("it should return an nil for an empty chain", t, func() { 72 hd = c.Top() 73 So(hd, ShouldBeNil) 74 hash, hd = c.TopType("entryTypeFoo") 75 So(hd, ShouldBeNil) 76 So(hash, ShouldBeNil) 77 }) 78 79 e := GobEntry{C: "some data"} 80 c.AddEntry(now, "entryTypeFoo", &e, key) 81 82 Convey("Top it should return the top header", t, func() { 83 hd = c.Top() 84 So(hd, ShouldEqual, c.Headers[0]) 85 }) 86 Convey("TopType should return nil for non existent type", t, func() { 87 hash, hd = c.TopType("otherData") 88 So(hd, ShouldBeNil) 89 So(hash, ShouldBeNil) 90 }) 91 Convey("TopType should return header for correct type", t, func() { 92 hash, hd = c.TopType("entryTypeFoo") 93 So(hd, ShouldEqual, c.Headers[0]) 94 }) 95 c.AddEntry(now, "otherData", &e, key) 96 Convey("TopType should return headers for both types", t, func() { 97 hash, hd = c.TopType("entryTypeFoo") 98 So(hd, ShouldEqual, c.Headers[0]) 99 hash, hd = c.TopType("otherData") 100 So(hd, ShouldEqual, c.Headers[1]) 101 }) 102 103 Convey("Nth should return the nth header", t, func() { 104 hd = c.Nth(1) 105 So(hd, ShouldEqual, c.Headers[0]) 106 }) 107 108 } 109 110 func TestChainTopType(t *testing.T) { 111 hashSpec, _, _ := chainTestSetup() 112 c := NewChain(hashSpec) 113 Convey("it should return nil for an empty chain", t, func() { 114 hash, hd := c.TopType("entryTypeFoo") 115 So(hd, ShouldBeNil) 116 So(hash, ShouldBeNil) 117 }) 118 Convey("it should return nil for an chain with no entries of the type", t, func() { 119 }) 120 } 121 122 func TestChainAddEntry(t *testing.T) { 123 hashSpec, key, now := chainTestSetup() 124 c := NewChain(hashSpec) 125 126 Convey("it should add nil to the chain", t, func() { 127 e := GobEntry{C: "some data"} 128 hash, err := c.AddEntry(now, "entryTypeFoo", &e, key) 129 So(err, ShouldBeNil) 130 So(len(c.Headers), ShouldEqual, 1) 131 So(len(c.Entries), ShouldEqual, 1) 132 So(c.TypeTops["entryTypeFoo"], ShouldEqual, 0) 133 So(hash.Equal(c.Hashes[0]), ShouldBeTrue) 134 }) 135 } 136 137 func TestChainGet(t *testing.T) { 138 hashSpec, key, now := chainTestSetup() 139 c := NewChain(hashSpec) 140 141 e1 := GobEntry{C: "some data"} 142 h1, _ := c.AddEntry(now, "entryTypeFoo", &e1, key) 143 hd1, err1 := c.Get(h1) 144 145 e2 := GobEntry{C: "some other data"} 146 h2, _ := c.AddEntry(now, "entryTypeFoo", &e2, key) 147 hd2, err2 := c.Get(h2) 148 149 Convey("it should get header by hash or by Entry hash", t, func() { 150 So(hd1, ShouldEqual, c.Headers[0]) 151 So(err1, ShouldBeNil) 152 153 ehd, err := c.GetEntryHeader(hd1.EntryLink) 154 So(ehd, ShouldEqual, c.Headers[0]) 155 So(err, ShouldBeNil) 156 157 So(hd2, ShouldEqual, c.Headers[1]) 158 So(err2, ShouldBeNil) 159 160 ehd, err = c.GetEntryHeader(hd2.EntryLink) 161 So(ehd, ShouldEqual, c.Headers[1]) 162 So(err, ShouldBeNil) 163 }) 164 165 Convey("it should get entry by hash", t, func() { 166 ed, et, err := c.GetEntry(hd1.EntryLink) 167 So(err, ShouldBeNil) 168 So(et, ShouldEqual, "entryTypeFoo") 169 So(fmt.Sprintf("%v", &e1), ShouldEqual, fmt.Sprintf("%v", ed)) 170 ed, et, err = c.GetEntry(hd2.EntryLink) 171 So(err, ShouldBeNil) 172 So(et, ShouldEqual, "entryTypeFoo") 173 So(fmt.Sprintf("%v", &e2), ShouldEqual, fmt.Sprintf("%v", ed)) 174 }) 175 176 Convey("it should return nil for non existent hash", t, func() { 177 hash, _ := NewHash("QmNiCwBNA8MWDADTFVq1BonUEJbS2SvjAoNkZZrhEwcuUi") 178 hd, err := c.Get(hash) 179 So(hd, ShouldBeNil) 180 So(err, ShouldEqual, ErrHashNotFound) 181 }) 182 } 183 184 func TestChainMarshalChain(t *testing.T) { 185 hashSpec, key, now := chainTestSetup() 186 c := NewChain(hashSpec) 187 var emptyStringList []string 188 189 e := GobEntry{C: "fake DNA"} 190 c.AddEntry(now, DNAEntryType, &e, key) 191 192 e = GobEntry{C: "fake agent entry"} 193 c.AddEntry(now, AgentEntryType, &e, key) 194 195 e = GobEntry{C: "some data"} 196 c.AddEntry(now, "entryTypeFoo1", &e, key) 197 198 e = GobEntry{C: "some other data"} 199 c.AddEntry(now, "entryTypeFoo2", &e, key) 200 201 e = GobEntry{C: "and more data"} 202 c.AddEntry(now, "entryTypeFoo3", &e, key) 203 204 e = GobEntry{C: "some private"} 205 c.AddEntry(now, "entryTypePrivate", &e, key) 206 207 Convey("it should be able to marshal and unmarshal full chain", t, func() { 208 var b bytes.Buffer 209 210 err := c.MarshalChain(&b, ChainMarshalFlagsNone, emptyStringList, emptyStringList) 211 So(err, ShouldBeNil) 212 flags, c1, err := UnmarshalChain(hashSpec, &b) 213 So(err, ShouldBeNil) 214 So(flags, ShouldEqual, ChainMarshalFlagsNone) 215 So(c1.String(), ShouldEqual, c.String()) 216 217 // confirm that internal structures are properly set up 218 for i := 0; i < len(c.Headers); i++ { 219 So(c.Hashes[i].String(), ShouldEqual, c1.Hashes[i].String()) 220 } 221 So(reflect.DeepEqual(c.TypeTops, c1.TypeTops), ShouldBeTrue) 222 So(reflect.DeepEqual(c.Hmap, c1.Hmap), ShouldBeTrue) 223 So(reflect.DeepEqual(c.Emap, c1.Emap), ShouldBeTrue) 224 So(reflect.DeepEqual(c.Entries, c1.Entries), ShouldBeTrue) 225 }) 226 227 Convey("it should be able to marshal and unmarshal specify types", t, func() { 228 var b bytes.Buffer 229 230 typeList := []string{AgentEntryType, "entryTypeFoo2"} 231 err := c.MarshalChain(&b, ChainMarshalFlagsNone, typeList, emptyStringList) 232 So(err, ShouldBeNil) 233 flags, c1, err := UnmarshalChain(hashSpec, &b) 234 So(err, ShouldBeNil) 235 So(flags, ShouldEqual, ChainMarshalFlagsNone) 236 So(len(c1.Entries), ShouldEqual, 3) 237 So(c1.Headers[0].Type, ShouldEqual, DNAEntryType) 238 So(c1.Headers[1].Type, ShouldEqual, AgentEntryType) 239 So(c1.Headers[2].Type, ShouldEqual, "entryTypeFoo2") 240 So(c1.TypeTops[AgentEntryType], ShouldEqual, 1) 241 So(c1.TypeTops["entryTypeFoo2"], ShouldEqual, 2) 242 }) 243 244 Convey("it should be able to marshal and unmarshal headers only", t, func() { 245 var b bytes.Buffer 246 247 err := c.MarshalChain(&b, ChainMarshalFlagsNoEntries, emptyStringList, emptyStringList) 248 So(err, ShouldBeNil) 249 flags, c1, err := UnmarshalChain(hashSpec, &b) 250 So(err, ShouldBeNil) 251 So(flags, ShouldEqual, ChainMarshalFlagsNoEntries) 252 253 So(len(c1.Hashes), ShouldEqual, len(c.Hashes)) 254 So(len(c1.Entries), ShouldEqual, 0) 255 256 // confirm that internal structures are properly set up 257 for i := 0; i < len(c.Headers); i++ { 258 So(c.Hashes[i].String(), ShouldEqual, c1.Hashes[i].String()) 259 } 260 261 So(reflect.DeepEqual(c.TypeTops, c1.TypeTops), ShouldBeTrue) 262 So(reflect.DeepEqual(c.Hmap, c1.Hmap), ShouldBeTrue) 263 So(reflect.DeepEqual(c.Emap, c1.Emap), ShouldBeTrue) 264 }) 265 266 Convey("it should be able to marshal and unmarshal entries only", t, func() { 267 var b bytes.Buffer 268 269 err := c.MarshalChain(&b, ChainMarshalFlagsNoHeaders, emptyStringList, emptyStringList) 270 So(err, ShouldBeNil) 271 flags, c1, err := UnmarshalChain(hashSpec, &b) 272 So(err, ShouldBeNil) 273 So(flags, ShouldEqual, ChainMarshalFlagsNoHeaders) 274 275 So(len(c1.Hashes), ShouldEqual, 0) 276 So(len(c1.Headers), ShouldEqual, 0) 277 So(len(c1.Entries), ShouldEqual, len(c1.Entries)) 278 So(len(c1.Emap), ShouldEqual, 0) 279 So(len(c1.TypeTops), ShouldEqual, 0) 280 So(len(c1.Emap), ShouldEqual, 0) 281 282 So(reflect.DeepEqual(c.Entries, c1.Entries), ShouldBeTrue) 283 }) 284 285 Convey("it should be able to marshal and unmarshal with omitted DNA", t, func() { 286 var b bytes.Buffer 287 288 err := c.MarshalChain(&b, ChainMarshalFlagsOmitDNA, emptyStringList, emptyStringList) 289 So(err, ShouldBeNil) 290 flags, c1, err := UnmarshalChain(hashSpec, &b) 291 So(err, ShouldBeNil) 292 So(flags, ShouldEqual, ChainMarshalFlagsOmitDNA) 293 So(c1.Entries[0].Content(), ShouldEqual, "") 294 c1.Entries[0].(*GobEntry).C = c.Entries[0].(*GobEntry).C 295 So(c1.String(), ShouldEqual, c.String()) 296 297 // confirm that internal structures are properly set up 298 for i := 0; i < len(c.Headers); i++ { 299 So(c.Hashes[i].String(), ShouldEqual, c1.Hashes[i].String()) 300 } 301 So(reflect.DeepEqual(c.TypeTops, c1.TypeTops), ShouldBeTrue) 302 So(reflect.DeepEqual(c.Hmap, c1.Hmap), ShouldBeTrue) 303 So(reflect.DeepEqual(c.Emap, c1.Emap), ShouldBeTrue) 304 So(reflect.DeepEqual(c.Entries, c1.Entries), ShouldBeTrue) 305 306 }) 307 308 Convey("it should be able to marshal with contents of private entries being redacted", t, func() { 309 var b bytes.Buffer 310 311 privateTypes := []string{"entryTypePrivate"} 312 err := c.MarshalChain(&b, ChainMarshalFlagsNone, emptyStringList, privateTypes) 313 So(err, ShouldBeNil) 314 _, c1, err := UnmarshalChain(hashSpec, &b) 315 So(err, ShouldBeNil) 316 So(len(c1.Headers), ShouldEqual, len(c.Headers)) 317 So(len(c1.Entries), ShouldEqual, len(c.Entries)) 318 So(c1.Entries[0].Content(), ShouldEqual, c.Entries[0].Content()) 319 So(c1.Entries[1].Content(), ShouldEqual, c.Entries[1].Content()) 320 So(c1.Entries[2].Content(), ShouldEqual, c.Entries[2].Content()) 321 So(c1.Entries[3].Content(), ShouldEqual, c.Entries[3].Content()) 322 So(c1.Entries[4].Content(), ShouldEqual, c.Entries[4].Content()) 323 So(c1.Entries[5].Content(), ShouldEqual, ChainMarshalPrivateEntryRedacted) 324 325 }) 326 327 } 328 329 func TestWalkChain(t *testing.T) { 330 hashSpec, key, now := chainTestSetup() 331 c := NewChain(hashSpec) 332 e := GobEntry{C: "some data"} 333 c.AddEntry(now, "entryTypeFoo1", &e, key) 334 335 e = GobEntry{C: "some other data"} 336 c.AddEntry(now, "entryTypeFoo2", &e, key) 337 338 e = GobEntry{C: "and more data"} 339 c.AddEntry(now, "entryTypeFoo3", &e, key) 340 341 Convey("it should walk back from the top through all entries", t, func() { 342 var x string 343 var i int 344 err := c.Walk(func(key *Hash, h *Header, entry Entry) error { 345 i++ 346 x += fmt.Sprintf("%d:%v ", i, entry.(*GobEntry).C) 347 return nil 348 }) 349 So(err, ShouldBeNil) 350 So(x, ShouldEqual, "1:and more data 2:some other data 3:some data ") 351 }) 352 } 353 354 func TestChainValidateChain(t *testing.T) { 355 hashSpec, key, now := chainTestSetup() 356 c := NewChain(hashSpec) 357 e := GobEntry{C: "some data"} 358 c.AddEntry(now, DNAEntryType, &e, key) 359 360 e = GobEntry{C: "some other data"} 361 c.AddEntry(now, AgentEntryType, &e, key) 362 363 e = GobEntry{C: "and more data"} 364 c.AddEntry(now, "entryTypeFoo1", &e, key) 365 366 Convey("it should validate", t, func() { 367 So(c.Validate(false), ShouldBeNil) 368 }) 369 370 Convey("it should fail to validate if we diddle some bits", t, func() { 371 c.Entries[0].(*GobEntry).C = "fish" // tweak 372 So(c.Validate(false).Error(), ShouldEqual, "entry hash mismatch at link 0") 373 So(c.Validate(true), ShouldBeNil) // test skipping entry validation 374 375 c.Entries[0].(*GobEntry).C = "some data" //restore 376 hash, _ := NewHash("QmY8Mzg9F69e5P9AoQPYat655HEhc1TVGs11tmfNSzkqh2") 377 c.Headers[1].TypeLink = hash // tweak 378 So(c.Validate(false).Error(), ShouldEqual, "header hash mismatch at link 1") 379 380 c.Headers[1].TypeLink = NullHash() //restore 381 c.Headers[0].Type = "entryTypeBar" //tweak 382 err := c.Validate(false) 383 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 384 385 c.Headers[0].Type = DNAEntryType // restore 386 t := c.Headers[0].Time // tweak 387 c.Headers[0].Time = time.Now() 388 err = c.Validate(false) 389 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 390 391 c.Headers[0].Time = t // restore 392 c.Headers[0].HeaderLink = c.Headers[0].EntryLink // tweak 393 err = c.Validate(false) 394 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 395 396 c.Headers[0].HeaderLink = NullHash() // restore 397 before := c.Headers[0].EntryLink 398 tweak := []byte(before) 399 tweak[5] = 3 // tweak 400 c.Headers[0].EntryLink = Hash(tweak) 401 err = c.Validate(false) 402 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 403 404 c.Headers[0].EntryLink = before // restore 405 val := c.Headers[0].Sig.S[0] 406 c.Headers[0].Sig.S[0] = 99 // tweak 407 err = c.Validate(false) 408 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 409 410 c.Headers[0].Sig.S[0] = val // restore 411 c.Headers[0].Change = "foo" // tweak 412 err = c.Validate(false) 413 So(err.Error(), ShouldEqual, "header hash mismatch at link 0") 414 415 }) 416 } 417 418 func TestChain2String(t *testing.T) { 419 hashSpec, key, now := chainTestSetup() 420 c := NewChain(hashSpec) 421 422 Convey("it should dump empty string for empty chain", t, func() { 423 So(c.String(), ShouldEqual, "") 424 }) 425 426 e := GobEntry{C: "some data"} 427 c.AddEntry(now, DNAEntryType, &e, key) 428 429 Convey("it should dump a chain to text", t, func() { 430 So(c.String(), ShouldNotEqual, "") 431 }) 432 } 433 434 func TestChain2JSON(t *testing.T) { 435 hashSpec, key, now := chainTestSetup() 436 c := NewChain(hashSpec) 437 438 Convey("it should dump empty JSON object for empty chain", t, func() { 439 json, err := c.JSON(0) 440 So(err, ShouldBeNil) 441 So(json, ShouldEqual, "{}") 442 }) 443 444 e := GobEntry{C: "dna entry"} 445 c.AddEntry(now, DNAEntryType, &e, key) 446 447 Convey("it should dump a DNA entry as JSON", t, func() { 448 json, err := c.JSON(0) 449 So(err, ShouldBeNil) 450 json = NormaliseJSON(json) 451 matched, err := regexp.MatchString(`{"%dna":{"header":{.*},"content":"dna entry"}}`, json) 452 So(err, ShouldBeNil) 453 So(matched, ShouldBeTrue) 454 }) 455 456 e = GobEntry{C: "agent entry"} 457 c.AddEntry(now, AgentEntryType, &e, key) 458 459 Convey("it should dump a Agent entry as JSON", t, func() { 460 json, err := c.JSON(0) 461 So(err, ShouldBeNil) 462 json = NormaliseJSON(json) 463 matched, err := regexp.MatchString(`{"%dna":{"header":{.*},"content":"dna entry"},"%agent":{"header":{.*},"content":"agent entry"}}`, json) 464 So(err, ShouldBeNil) 465 So(matched, ShouldBeTrue) 466 }) 467 468 e = GobEntry{C: "chain entry"} 469 c.AddEntry(now, "handle", &e, key) 470 471 Convey("it should dump chain with entries as JSON", t, func() { 472 json, err := c.JSON(0) 473 So(err, ShouldBeNil) 474 json = NormaliseJSON(json) 475 matched, err := regexp.MatchString(`{"%dna":{.*},"%agent":{.*},"entries":\[{"header":{"type":"handle",.*"},"content":"chain entry"}\]}`, json) 476 So(err, ShouldBeNil) 477 So(matched, ShouldBeTrue) 478 }) 479 480 e.C = "chain entry 2" 481 c.AddEntry(now, "handle", &e, key) 482 e.C = "chain entry 3" 483 c.AddEntry(now, "handle", &e, key) 484 Convey("it should dump chain from the given start index", t, func() { 485 json, err := c.JSON(2) 486 So(err, ShouldBeNil) 487 json = NormaliseJSON(json) 488 matched, err := regexp.MatchString(`{"entries":\[{"header":{"type":"handle",.*"},"content":"chain entry 2"},{"header":{"type":"handle",.*"},"content":"chain entry 3"}\]}`, json) 489 So(err, ShouldBeNil) 490 So(matched, ShouldBeTrue) 491 }) 492 } 493 494 func TestChain2Dot(t *testing.T) { 495 hashSpec, key, now := chainTestSetup() 496 c := NewChain(hashSpec) 497 498 Convey("it should dump an empty 'dot' document for empty chain", t, func() { 499 dot, err := c.Dot(0) 500 So(err, ShouldBeNil) 501 matched, err := regexp.MatchString(`digraph chain {.*}`, strings.Replace(dot, "\n", "", -1)) 502 So(err, ShouldBeNil) 503 So(matched, ShouldBeTrue) 504 So(dot, ShouldNotContainSubstring, "header") 505 So(dot, ShouldNotContainSubstring, "content") 506 }) 507 508 e := GobEntry{C: "dna entry"} 509 c.AddEntry(now, DNAEntryType, &e, key) 510 511 Convey("after adding the dna, the dump should include the genesis entry in 'dot' format", t, func() { 512 dot, err := c.Dot(0) 513 So(err, ShouldBeNil) 514 515 hdr := c.Headers[0] 516 timestamp := fmt.Sprintf("%v", hdr.Time) 517 hdrType := fmt.Sprintf("%v", hdr.Type) 518 hdrEntry := fmt.Sprintf("%v", hdr.EntryLink) 519 nextHeader := fmt.Sprintf("%v", hdr.HeaderLink) 520 next := fmt.Sprintf("%s: %v", hdr.Type, hdr.TypeLink) 521 hash := fmt.Sprintf("%s", c.Hashes[0]) 522 523 expectedDot := `header0 [label=<{HEADER 0: GENESIS| 524 {Type|` + hdrType + `}| 525 {Hash|` + hash + `}| 526 {Timestamp|` + timestamp + `}| 527 {Next Header|` + nextHeader + `}| 528 {Next|` + next + `}| 529 {Entry|` + hdrEntry + `} 530 }>]; 531 content0 [label=<{HOLOCHAIN DNA|See dna.json}>]; 532 header0->content0;` 533 534 So(dot, ShouldContainSubstring, expectedDot) 535 }) 536 537 e = GobEntry{C: `{"Identity":"lucy","Revocation":"","PublicKey":"XYZ"}`} 538 c.AddEntry(now, AgentEntryType, &e, key) 539 540 Convey("after adding the agent, the dump should include the agent entry in 'dot' format", t, func() { 541 dot, err := c.Dot(0) 542 So(err, ShouldBeNil) 543 544 hdr0 := c.Headers[0] 545 timestamp0 := fmt.Sprintf("%v", hdr0.Time) 546 hdrType0 := fmt.Sprintf("%v", hdr0.Type) 547 hdrEntry0 := fmt.Sprintf("%v", hdr0.EntryLink) 548 nextHeader0 := fmt.Sprintf("%v", hdr0.HeaderLink) 549 next0 := fmt.Sprintf("%s: %v", hdr0.Type, hdr0.TypeLink) 550 hash0 := fmt.Sprintf("%s", c.Hashes[0]) 551 552 hdr1 := c.Headers[1] 553 timestamp1 := fmt.Sprintf("%v", hdr1.Time) 554 hdrType1 := fmt.Sprintf("%v", hdr1.Type) 555 hdrEntry1 := fmt.Sprintf("%v", hdr1.EntryLink) 556 nextHeader1 := fmt.Sprintf("%v", hdr1.HeaderLink) 557 next1 := fmt.Sprintf("%s: %v", hdr1.Type, hdr1.TypeLink) 558 hash1 := fmt.Sprintf("%s", c.Hashes[1]) 559 560 expectedDot := `header0 [label=<{HEADER 0: GENESIS| 561 {Type|` + hdrType0 + `}| 562 {Hash|` + hash0 + `}| 563 {Timestamp|` + timestamp0 + `}| 564 {Next Header|` + nextHeader0 + `}| 565 {Next|` + next0 + `}| 566 {Entry|` + hdrEntry0 + `} 567 }>]; 568 content0 [label=<{HOLOCHAIN DNA|See dna.json}>]; 569 header0->content0; 570 header0->header1; 571 header1 [label=<{HEADER 1| 572 {Type|` + hdrType1 + `}| 573 {Hash|` + hash1 + `}| 574 {Timestamp|` + timestamp1 + `}| 575 {Next Header|` + nextHeader1 + `}| 576 {Next|` + next1 + `}| 577 {Entry|` + hdrEntry1 + `} 578 }>]; 579 content1 [label=<{AGENT ID|\{"Identity":"lucy",<br/>"Revocation":"",<br/>"PublicKey":"XYZ"\}}>]; 580 header1->content1;` 581 582 So(dot, ShouldContainSubstring, expectedDot) 583 }) 584 585 e = GobEntry{C: `{"Links":[{"Base":"ABC","Link":"XYZ","Tag":"handle"}]}`} 586 c.AddEntry(now, "handle", &e, key) 587 588 Convey("after adding an entry, the dump should include the entry in 'dot' format", t, func() { 589 dot, err := c.Dot(0) 590 So(err, ShouldBeNil) 591 592 hdr0 := c.Headers[0] 593 timestamp0 := fmt.Sprintf("%v", hdr0.Time) 594 hdrType0 := fmt.Sprintf("%v", hdr0.Type) 595 hdrEntry0 := fmt.Sprintf("%v", hdr0.EntryLink) 596 nextHeader0 := fmt.Sprintf("%v", hdr0.HeaderLink) 597 next0 := fmt.Sprintf("%s: %v", hdr0.Type, hdr0.TypeLink) 598 hash0 := fmt.Sprintf("%s", c.Hashes[0]) 599 600 hdr1 := c.Headers[1] 601 timestamp1 := fmt.Sprintf("%v", hdr1.Time) 602 hdrType1 := fmt.Sprintf("%v", hdr1.Type) 603 hdrEntry1 := fmt.Sprintf("%v", hdr1.EntryLink) 604 nextHeader1 := fmt.Sprintf("%v", hdr1.HeaderLink) 605 next1 := fmt.Sprintf("%s: %v", hdr1.Type, hdr1.TypeLink) 606 hash1 := fmt.Sprintf("%s", c.Hashes[1]) 607 608 hdr2 := c.Headers[2] 609 timestamp2 := fmt.Sprintf("%v", hdr2.Time) 610 hdrType2 := fmt.Sprintf("%v", hdr2.Type) 611 hdrEntry2 := fmt.Sprintf("%v", hdr2.EntryLink) 612 nextHeader2 := fmt.Sprintf("%v", hdr2.HeaderLink) 613 next2 := fmt.Sprintf("%s: %v", hdr2.Type, hdr2.TypeLink) 614 hash2 := fmt.Sprintf("%s", c.Hashes[2]) 615 616 expectedDot := `header0 [label=<{HEADER 0: GENESIS| 617 {Type|` + hdrType0 + `}| 618 {Hash|` + hash0 + `}| 619 {Timestamp|` + timestamp0 + `}| 620 {Next Header|` + nextHeader0 + `}| 621 {Next|` + next0 + `}| 622 {Entry|` + hdrEntry0 + `} 623 }>]; 624 content0 [label=<{HOLOCHAIN DNA|See dna.json}>]; 625 header0->content0; 626 header0->header1; 627 header1 [label=<{HEADER 1| 628 {Type|` + hdrType1 + `}| 629 {Hash|` + hash1 + `}| 630 {Timestamp|` + timestamp1 + `}| 631 {Next Header|` + nextHeader1 + `}| 632 {Next|` + next1 + `}| 633 {Entry|` + hdrEntry1 + `} 634 }>]; 635 content1 [label=<{AGENT ID|\{"Identity":"lucy",<br/>"Revocation":"",<br/>"PublicKey":"XYZ"\}}>]; 636 header1->content1; 637 header1->header2; 638 header2 [label=<{HEADER 2| 639 {Type|` + hdrType2 + `}| 640 {Hash|` + hash2 + `}| 641 {Timestamp|` + timestamp2 + `}| 642 {Next Header|` + nextHeader2 + `}| 643 {Next|` + next2 + `}| 644 {Entry|` + hdrEntry2 + `} 645 }>]; 646 content2 [label=<{ENTRY 2|\{"Links":[<br/>\{"Base":"ABC",<br/>"Link":"XYZ",<br/>"Tag":"handle"\}]\}}>]; 647 header2->content2;` 648 649 So(dot, ShouldContainSubstring, expectedDot) 650 }) 651 652 Convey("only entries starting from the specified index should be dumped", t, func() { 653 dot, err := c.Dot(2) 654 So(err, ShouldBeNil) 655 656 hdr2 := c.Headers[2] 657 timestamp2 := fmt.Sprintf("%v", hdr2.Time) 658 hdrType2 := fmt.Sprintf("%v", hdr2.Type) 659 hdrEntry2 := fmt.Sprintf("%v", hdr2.EntryLink) 660 nextHeader2 := fmt.Sprintf("%v", hdr2.HeaderLink) 661 next2 := fmt.Sprintf("%s: %v", hdr2.Type, hdr2.TypeLink) 662 hash2 := fmt.Sprintf("%s", c.Hashes[2]) 663 664 expectedDot := `header2 [label=<{HEADER 2| 665 {Type|` + hdrType2 + `}| 666 {Hash|` + hash2 + `}| 667 {Timestamp|` + timestamp2 + `}| 668 {Next Header|` + nextHeader2 + `}| 669 {Next|` + next2 + `}| 670 {Entry|` + hdrEntry2 + `} 671 }>]; 672 content2 [label=<{ENTRY 2|\{"Links":[<br/>\{"Base":"ABC",<br/>"Link":"XYZ",<br/>"Tag":"handle"\}]\}}>]; 673 header2->content2;` 674 675 So(dot, ShouldContainSubstring, expectedDot) 676 }) 677 } 678 679 func TestChainBundle(t *testing.T) { 680 hashSpec, key, now := chainTestSetup() 681 c := NewChain(hashSpec) 682 e := GobEntry{C: "fake DNA"} 683 c.AddEntry(now, DNAEntryType, &e, key) 684 e = GobEntry{C: "foo data"} 685 c.AddEntry(now, "entryTypeFoo2", &e, key) 686 687 Convey("starting a bundle should set the bundle start point", t, func() { 688 So(c.BundleStarted(), ShouldBeNil) 689 err := c.StartBundle("myBundle") 690 So(err, ShouldBeNil) 691 bundle := c.BundleStarted() 692 So(bundle, ShouldNotBeNil) 693 So(bundle.idx, ShouldEqual, c.Length()-1) 694 So(bundle.userParam, ShouldEqual, `"myBundle"`) // should convert user param to json 695 So(bundle.chain.bundleOf, ShouldEqual, c) 696 }) 697 698 Convey("it should add entries to the bundle chain", t, func() { 699 700 e := GobEntry{C: "some data"} 701 702 bundle := c.BundleStarted() 703 So(bundle.chain.Length(), ShouldEqual, 0) 704 705 now := now.Round(0) 706 l, hash, header, err := bundle.chain.prepareHeader(now, "entryTypeFoo1", &e, key, NullHash()) 707 So(err, ShouldBeNil) 708 So(l, ShouldEqual, 0) 709 710 err = bundle.chain.addEntry(l, hash, header, &e) 711 So(err, ShouldBeNil) 712 So(bundle.chain.Length(), ShouldEqual, 1) 713 714 e = GobEntry{C: "another entry"} 715 _, err = bundle.chain.AddEntry(now, "entryTypeFoo2", &e, key) 716 So(err, ShouldBeNil) 717 So(bundle.chain.Length(), ShouldEqual, 2) 718 }) 719 720 Convey("you shouldn't be able to work on a chain when bundle opened", t, func() { 721 l, hash, header, err := c.prepareHeader(now, "entryTypeFoo1", &e, key, NullHash()) 722 So(err, ShouldEqual, ErrChainLockedForBundle) 723 724 err = c.addEntry(l, hash, header, &e) 725 So(err, ShouldEqual, ErrChainLockedForBundle) 726 }) 727 728 Convey("it should add entries to the main chain when bundle closed and validate!", t, func() { 729 So(c.Length(), ShouldEqual, 2) 730 err := c.CloseBundle(true) 731 So(err, ShouldBeNil) 732 So(c.BundleStarted(), ShouldBeNil) 733 So(c.Length(), ShouldEqual, 4) 734 So(c.Validate(false), ShouldBeNil) 735 736 // makes sure type linking worked too 737 hash, _ := c.TopType("entryTypeFoo1") 738 So(hash.String(), ShouldEqual, c.Hashes[2].String()) 739 hash, _ = c.TopType("entryTypeFoo2") 740 So(hash.String(), ShouldEqual, c.Hashes[3].String()) 741 742 }) 743 744 Convey("it should not add entries to the main chain when bundle closed without commit!", t, func() { 745 So(c.Length(), ShouldEqual, 4) 746 err := c.StartBundle("myBundle") 747 e = GobEntry{C: "another entry"} 748 _, err = c.bundle.chain.AddEntry(now, "entryTypeFoo2", &e, key) 749 So(c.bundle.chain.Length(), ShouldEqual, 1) 750 err = c.CloseBundle(false) 751 So(err, ShouldBeNil) 752 So(c.Length(), ShouldEqual, 4) 753 }) 754 } 755 756 /* 757 func TestPersistingChain(t *testing.T) { 758 hashSpec, key, now := chainTestSetup() 759 c := NewChain(hashSpec) 760 var b bytes.Buffer 761 c.encoder = gob.NewEncoder(&b) 762 763 h, key, now := chainTestSetup() 764 e := GobEntry{C: "some data"} 765 c.AddEntry(now, "entryTypeFoo1", &e, key) 766 767 e = GobEntry{C: "some other data"} 768 c.AddEntry(now, "entryTypeFoo1", &e, key) 769 770 e = GobEntry{C: "and more data"} 771 c.AddEntry(now, "entryTypeFoo1", &e, key) 772 773 dec := gob.NewDecoder(&b) 774 775 var header *Header 776 var entry Entry 777 header, entry, err := readPair(dec) 778 779 Convey("it should have added items to the writer", t, func() { 780 So(err, ShouldBeNil) 781 So(fmt.Sprintf("%v", header), ShouldEqual, fmt.Sprintf("%v", c.Headers[0])) 782 So(fmt.Sprintf("%v", entry), ShouldEqual, fmt.Sprintf("%v", c.Entries[0])) 783 }) 784 } 785 */