Skip to content

Commit 9f91e5a

Browse files
committed
feat: added more test coverage
1 parent 55c3dca commit 9f91e5a

File tree

5 files changed

+351
-0
lines changed

5 files changed

+351
-0
lines changed

inpoints_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package subtree
22

33
import (
4+
"bytes"
45
"testing"
56

67
"github.com/bsv-blockchain/go-bt/v2/chainhash"
@@ -119,6 +120,62 @@ func TestGetParentVoutsAtIndex(t *testing.T) {
119120
})
120121
}
121122

123+
func TestString(t *testing.T) {
124+
p, err := NewTxInpointsFromTx(tx)
125+
require.NoError(t, err)
126+
127+
// Test String method
128+
str := p.String()
129+
assert.NotEmpty(t, str)
130+
assert.Contains(t, str, "TxInpoints")
131+
assert.Contains(t, str, "ParentTxHashes")
132+
assert.Contains(t, str, "Idxs")
133+
}
134+
135+
func TestLen32(t *testing.T) {
136+
t.Run("nil slice", func(t *testing.T) {
137+
var nilSlice []int
138+
result := len32(nilSlice)
139+
assert.Equal(t, uint32(0), result)
140+
})
141+
142+
t.Run("normal slice", func(t *testing.T) {
143+
normalSlice := []int{1, 2, 3, 4, 5}
144+
result := len32(normalSlice)
145+
assert.Equal(t, uint32(5), result)
146+
})
147+
148+
t.Run("empty slice", func(t *testing.T) {
149+
emptySlice := make([]int, 0)
150+
result := len32(emptySlice)
151+
assert.Equal(t, uint32(0), result)
152+
})
153+
}
154+
155+
func TestNewTxInpointsFromBytesError(t *testing.T) {
156+
t.Run("invalid bytes", func(t *testing.T) {
157+
invalidBytes := []byte{0x01, 0x02, 0x03}
158+
_, err := NewTxInpointsFromBytes(invalidBytes)
159+
require.Error(t, err)
160+
})
161+
162+
t.Run("empty bytes", func(t *testing.T) {
163+
emptyBytes := make([]byte, 4)
164+
// This creates bytes representing 0 parent inpoints
165+
p, err := NewTxInpointsFromBytes(emptyBytes)
166+
require.NoError(t, err)
167+
assert.Empty(t, p.ParentTxHashes)
168+
})
169+
}
170+
171+
func TestNewTxInpointsFromReaderError(t *testing.T) {
172+
t.Run("invalid reader", func(t *testing.T) {
173+
invalidBytes := []byte{0x01, 0x02, 0x03}
174+
_, err := NewTxInpointsFromReader(bytes.NewReader(invalidBytes))
175+
require.Error(t, err)
176+
})
177+
}
178+
122179
func BenchmarkNewTxInpoints(b *testing.B) {
123180
for i := 0; i < b.N; i++ {
124181
_, err := NewTxInpointsFromTx(tx)

merkle_tree_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,94 @@ func TestGetMerkleProofForCoinbase(t *testing.T) {
5050
require.NoError(t, topTree.AddNode(*subtree2.RootHash(), subtree2.Fees, subtree1.SizeInBytes))
5151
assert.Equal(t, expectedRootHash, topTree.RootHash().String())
5252
})
53+
54+
t.Run("empty subtrees", func(t *testing.T) {
55+
_, err := GetMerkleProofForCoinbase([]*Subtree{})
56+
require.Error(t, err)
57+
assert.Contains(t, err.Error(), "no subtrees available")
58+
})
59+
60+
t.Run("error from GetMerkleProof", func(t *testing.T) {
61+
// Create a subtree with no nodes to trigger error
62+
subtree, err := NewTree(2)
63+
require.NoError(t, err)
64+
65+
_, err = GetMerkleProofForCoinbase([]*Subtree{subtree})
66+
require.Error(t, err)
67+
})
68+
}
69+
70+
func TestBuildMerkleTreeStoreFromBytes(t *testing.T) {
71+
t.Run("empty nodes", func(t *testing.T) {
72+
merkles, err := BuildMerkleTreeStoreFromBytes([]Node{})
73+
require.NoError(t, err)
74+
assert.NotNil(t, merkles)
75+
assert.Empty(t, *merkles)
76+
})
77+
78+
t.Run("single node", func(t *testing.T) {
79+
hash1, _ := chainhash.NewHashFromStr("97af9ad3583e2f83fc1e44e475e3a3ee31ec032449cc88b491479ef7d187c115")
80+
nodes := []Node{
81+
{Hash: *hash1, Fee: 100, SizeInBytes: 200},
82+
}
83+
84+
merkles, err := BuildMerkleTreeStoreFromBytes(nodes)
85+
require.NoError(t, err)
86+
assert.NotNil(t, merkles)
87+
// Single node case returns the hash itself
88+
assert.Len(t, *merkles, 1)
89+
assert.Equal(t, *hash1, (*merkles)[0])
90+
})
91+
92+
t.Run("two nodes", func(t *testing.T) {
93+
hash1, _ := chainhash.NewHashFromStr("97af9ad3583e2f83fc1e44e475e3a3ee31ec032449cc88b491479ef7d187c115")
94+
hash2, _ := chainhash.NewHashFromStr("7ce05dda56bc523048186c0f0474eb21c92fe35de6d014bd016834637a3ed08d")
95+
nodes := []Node{
96+
{Hash: *hash1, Fee: 100, SizeInBytes: 200},
97+
{Hash: *hash2, Fee: 150, SizeInBytes: 250},
98+
}
99+
100+
merkles, err := BuildMerkleTreeStoreFromBytes(nodes)
101+
require.NoError(t, err)
102+
assert.NotNil(t, merkles)
103+
assert.Len(t, *merkles, 1)
104+
})
105+
106+
t.Run("multiple nodes", func(t *testing.T) {
107+
hash1, _ := chainhash.NewHashFromStr("97af9ad3583e2f83fc1e44e475e3a3ee31ec032449cc88b491479ef7d187c115")
108+
hash2, _ := chainhash.NewHashFromStr("7ce05dda56bc523048186c0f0474eb21c92fe35de6d014bd016834637a3ed08d")
109+
hash3, _ := chainhash.NewHashFromStr("3070fb937289e24720c827cbc24f3fce5c361cd7e174392a700a9f42051609e0")
110+
hash4, _ := chainhash.NewHashFromStr("d3cde0ab7142cc99acb31c5b5e1e941faed1c5cf5f8b63ed663972845d663487")
111+
nodes := []Node{
112+
{Hash: *hash1, Fee: 100, SizeInBytes: 200},
113+
{Hash: *hash2, Fee: 150, SizeInBytes: 250},
114+
{Hash: *hash3, Fee: 175, SizeInBytes: 275},
115+
{Hash: *hash4, Fee: 200, SizeInBytes: 300},
116+
}
117+
118+
merkles, err := BuildMerkleTreeStoreFromBytes(nodes)
119+
require.NoError(t, err)
120+
assert.NotNil(t, merkles)
121+
// For 4 nodes, we need 3 merkle hashes (one for the root)
122+
assert.Len(t, *merkles, 3)
123+
})
124+
125+
t.Run("large tree with parallel processing", func(t *testing.T) {
126+
// Create more than 1024 nodes to trigger parallel processing
127+
nodes := make([]Node, 2048)
128+
for i := 0; i < 2048; i++ {
129+
hash := chainhash.HashH([]byte{byte(i >> 8), byte(i)})
130+
nodes[i] = Node{
131+
Hash: hash,
132+
Fee: uint64(i), //nolint:gosec // G115: Safe conversion, i is limited to 2048
133+
SizeInBytes: uint64(i * 10), //nolint:gosec // G115: Safe conversion, i*10 is limited to 20480
134+
}
135+
}
136+
137+
merkles, err := BuildMerkleTreeStoreFromBytes(nodes)
138+
require.NoError(t, err)
139+
assert.NotNil(t, merkles)
140+
// For 2048 nodes, we expect 2047 merkle hashes
141+
assert.Len(t, *merkles, 2047)
142+
})
53143
}

subtree_data_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,75 @@ func TestNewSubtreeDataFromReader(t *testing.T) {
314314
})
315315
}
316316

317+
func TestDataRootHash(t *testing.T) {
318+
t.Run("get root hash from data", func(t *testing.T) {
319+
_, subtreeData := setupData(t)
320+
321+
// Get the root hash
322+
rootHash := subtreeData.RootHash()
323+
require.NotNil(t, rootHash)
324+
325+
// Verify it matches the subtree's root hash
326+
expectedRootHash := subtreeData.Subtree.RootHash()
327+
assert.Equal(t, expectedRootHash, rootHash)
328+
})
329+
}
330+
331+
func TestSerializeFromReaderErrors(t *testing.T) {
332+
t.Run("nil subtree", func(t *testing.T) {
333+
data := &Data{
334+
Subtree: nil,
335+
Txs: make([]*bt.Tx, 0),
336+
}
337+
338+
// Should fail with nil subtree
339+
err := data.serializeFromReader(bytes.NewReader([]byte{}))
340+
require.Error(t, err)
341+
assert.Contains(t, err.Error(), "subtree nodes slice is empty")
342+
})
343+
344+
t.Run("empty subtree nodes", func(t *testing.T) {
345+
subtree, err := NewTree(2)
346+
require.NoError(t, err)
347+
348+
data := &Data{
349+
Subtree: subtree,
350+
Txs: make([]*bt.Tx, 0),
351+
}
352+
353+
// Should fail with empty nodes
354+
err = data.serializeFromReader(bytes.NewReader([]byte{}))
355+
require.Error(t, err)
356+
assert.Contains(t, err.Error(), "subtree nodes slice is empty")
357+
})
358+
359+
t.Run("tx index out of bounds", func(t *testing.T) {
360+
tx1 := tx.Clone()
361+
tx1.Version = 1
362+
363+
subtree, err := NewTree(2)
364+
require.NoError(t, err)
365+
_ = subtree.AddNode(*tx1.TxIDChainHash(), 111, 0)
366+
367+
data := &Data{
368+
Subtree: subtree,
369+
Txs: make([]*bt.Tx, 1),
370+
}
371+
372+
// Create multiple transactions to cause index out of bounds
373+
var txBytes []byte
374+
for i := 0; i < 10; i++ {
375+
txClone := tx.Clone()
376+
txClone.Version = uint32(i + 1) //nolint:gosec // G115: Safe conversion, i is limited to 10
377+
txBytes = append(txBytes, txClone.Bytes()...)
378+
}
379+
380+
err = data.serializeFromReader(bytes.NewReader(txBytes))
381+
require.Error(t, err)
382+
assert.Contains(t, err.Error(), "transaction index out of bounds")
383+
})
384+
}
385+
317386
// Mock reader for testing
318387
type mockReader struct {
319388
err error

subtree_meta_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,62 @@ func initMeta(t *testing.T) ([]*bt.Tx, *Subtree, *Meta) {
342342

343343
return []*bt.Tx{tx1, tx2, tx3, tx4}, subtree, subtreeMeta
344344
}
345+
346+
func TestNewSubtreeMetaFromBytesErrors(t *testing.T) {
347+
t.Run("invalid bytes", func(t *testing.T) {
348+
subtree, err := NewTreeByLeafCount(4)
349+
require.NoError(t, err)
350+
351+
invalidBytes := []byte{0x01, 0x02, 0x03}
352+
_, err = NewSubtreeMetaFromBytes(subtree, invalidBytes)
353+
require.Error(t, err)
354+
})
355+
}
356+
357+
func TestNewSubtreeMetaFromReaderErrors(t *testing.T) {
358+
t.Run("invalid reader", func(t *testing.T) {
359+
subtree, err := NewTreeByLeafCount(4)
360+
require.NoError(t, err)
361+
362+
invalidBytes := []byte{0x01, 0x02, 0x03}
363+
_, err = NewSubtreeMetaFromReader(subtree, bytes.NewReader(invalidBytes))
364+
require.Error(t, err)
365+
})
366+
}
367+
368+
func TestMetaSerializeErrors(t *testing.T) {
369+
t.Run("nil subtree", func(t *testing.T) {
370+
meta := &Meta{
371+
Subtree: nil,
372+
TxInpoints: make([]TxInpoints, 0),
373+
}
374+
375+
// Should fail with nil subtree
376+
_, err := meta.Serialize()
377+
require.Error(t, err)
378+
assert.Contains(t, err.Error(), "cannot serialize, subtree is not set")
379+
})
380+
381+
t.Run("missing parent tx hashes for node", func(t *testing.T) {
382+
tx1 := tx.Clone()
383+
tx1.Version = 1
384+
385+
tx2 := tx.Clone()
386+
tx2.Version = 2
387+
388+
subtree, err := NewTreeByLeafCount(4)
389+
require.NoError(t, err)
390+
require.NoError(t, subtree.AddNode(*tx1.TxIDChainHash(), 1, 1))
391+
require.NoError(t, subtree.AddNode(*tx2.TxIDChainHash(), 2, 2))
392+
393+
meta := NewSubtreeMeta(subtree)
394+
395+
// Set inpoints for first tx but not for the second
396+
require.NoError(t, meta.SetTxInpointsFromTx(tx1))
397+
398+
// Should fail because second node doesn't have parent tx hashes set
399+
_, err = meta.Serialize()
400+
require.Error(t, err)
401+
assert.Contains(t, err.Error(), "parent tx hashes are not set for node")
402+
})
403+
}

subtree_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package subtree
22

33
import (
44
"bytes"
5+
"encoding/binary"
56
"fmt"
67
"testing"
78

@@ -944,6 +945,81 @@ func TestAddNode(t *testing.T) {
944945
})
945946
}
946947

948+
func TestNewSubtreeFromBytesErrors(t *testing.T) {
949+
t.Run("invalid bytes", func(t *testing.T) {
950+
invalidBytes := []byte{0x01, 0x02, 0x03}
951+
_, err := NewSubtreeFromBytes(invalidBytes)
952+
require.Error(t, err)
953+
})
954+
}
955+
956+
func TestNewSubtreeFromReaderErrors(t *testing.T) {
957+
t.Run("invalid reader", func(t *testing.T) {
958+
invalidBytes := []byte{0x01, 0x02, 0x03}
959+
_, err := NewSubtreeFromReader(bytes.NewReader(invalidBytes))
960+
require.Error(t, err)
961+
})
962+
}
963+
964+
func TestDeserializeFromReaderErrors(t *testing.T) {
965+
t.Run("corrupted root hash", func(t *testing.T) {
966+
st := &Subtree{}
967+
// Only 10 bytes instead of expected 32 for hash
968+
invalidData := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A}
969+
err := st.DeserializeFromReader(bytes.NewReader(invalidData))
970+
require.Error(t, err)
971+
assert.Contains(t, err.Error(), "unable to read root hash")
972+
})
973+
974+
t.Run("corrupted fees", func(t *testing.T) {
975+
st := &Subtree{}
976+
// 32 bytes for hash but not enough for fees
977+
invalidData := make([]byte, 32)
978+
err := st.DeserializeFromReader(bytes.NewReader(invalidData))
979+
require.Error(t, err)
980+
assert.Contains(t, err.Error(), "unable to read fees")
981+
})
982+
983+
t.Run("corrupted sizeInBytes", func(t *testing.T) {
984+
st := &Subtree{}
985+
// 32 bytes for hash + 8 for fees but not enough for sizeInBytes
986+
invalidData := make([]byte, 40)
987+
err := st.DeserializeFromReader(bytes.NewReader(invalidData))
988+
require.Error(t, err)
989+
assert.Contains(t, err.Error(), "unable to read sizeInBytes")
990+
})
991+
992+
t.Run("corrupted node data", func(t *testing.T) {
993+
st := &Subtree{}
994+
// Create valid header but invalid node data
995+
buf := bytes.NewBuffer(nil)
996+
997+
// Write root hash (32 bytes)
998+
rootHash := make([]byte, 32)
999+
buf.Write(rootHash)
1000+
1001+
// Write fees (8 bytes)
1002+
feesBytes := make([]byte, 8)
1003+
buf.Write(feesBytes)
1004+
1005+
// Write sizeInBytes (8 bytes)
1006+
sizeBytes := make([]byte, 8)
1007+
buf.Write(sizeBytes)
1008+
1009+
// Write number of leaves (8 bytes) - say we have 2 nodes
1010+
numLeavesBytes := make([]byte, 8)
1011+
binary.LittleEndian.PutUint64(numLeavesBytes, 2)
1012+
buf.Write(numLeavesBytes)
1013+
1014+
// Write incomplete node data (should be 48 bytes per node, but only write 10)
1015+
buf.Write(make([]byte, 10))
1016+
1017+
err := st.DeserializeFromReader(buf)
1018+
require.Error(t, err)
1019+
assert.Contains(t, err.Error(), "unable to read node")
1020+
})
1021+
}
1022+
9471023
func TestSubtree_ConflictingNodes(t *testing.T) {
9481024
tx1 := tx.Clone()
9491025
tx1.Version = 1

0 commit comments

Comments
 (0)