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  */