go-blockchain/tui/node_test.go

152 lines
3.6 KiB
Go
Raw Normal View History

// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2
package tui
import (
"testing"
"time"
store "dappco.re/go/core/store"
"dappco.re/go/core/blockchain/chain"
"dappco.re/go/core/blockchain/types"
"dappco.re/go/core/blockchain/wire"
)
// testCoinbaseTx returns a minimal v1 coinbase transaction that round-trips
// cleanly through the wire encoder/decoder.
func testCoinbaseTx(height uint64) types.Transaction {
return types.Transaction{
Version: 1,
Vin: []types.TxInput{types.TxInputGenesis{Height: height}},
Vout: []types.TxOutput{types.TxOutputBare{
Amount: 1000000,
Target: types.TxOutToKey{Key: types.PublicKey{0x01}},
}},
Extra: wire.EncodeVarint(0),
Attachment: wire.EncodeVarint(0),
}
}
// seedChain creates an in-memory chain with n blocks.
func seedChain(t *testing.T, n int) *chain.Chain {
t.Helper()
s, err := store.New(":memory:")
if err != nil {
t.Fatalf("store.New: %v", err)
}
t.Cleanup(func() { s.Close() })
c := chain.New(s)
for i := 0; i < n; i++ {
blk := &types.Block{
BlockHeader: types.BlockHeader{
MajorVersion: 1,
Timestamp: 1770897600 + uint64(i)*120,
},
MinerTx: testCoinbaseTx(uint64(i)),
}
meta := &chain.BlockMeta{
Hash: types.Hash{byte(i)},
Height: uint64(i),
Timestamp: 1770897600 + uint64(i)*120,
Difficulty: 1000 + uint64(i)*100,
}
if err := c.PutBlock(blk, meta); err != nil {
t.Fatalf("PutBlock(%d): %v", i, err)
}
}
return c
}
func TestNewNode_Good(t *testing.T) {
c := seedChain(t, 1)
n := NewNode(c)
if n == nil {
t.Fatal("NewNode returned nil")
}
if n.Chain() != c {
t.Error("Chain() does not return the same chain passed to NewNode")
}
}
func TestNode_Status_Good(t *testing.T) {
c := seedChain(t, 10)
n := NewNode(c)
status, err := n.Status()
if err != nil {
t.Fatalf("Status: %v", err)
}
if status.Height != 10 {
t.Errorf("height: got %d, want 10", status.Height)
}
// Top block is at height 9, difficulty = 1000 + 9*100 = 1900.
if status.Difficulty != 1900 {
t.Errorf("difficulty: got %d, want 1900", status.Difficulty)
}
wantHash := types.Hash{9}
if status.TopHash != wantHash {
t.Errorf("top hash: got %x, want %x", status.TopHash, wantHash)
}
wantTime := time.Unix(int64(1770897600+9*120), 0)
if !status.TipTime.Equal(wantTime) {
t.Errorf("tip time: got %v, want %v", status.TipTime, wantTime)
}
}
func TestNode_Status_Empty(t *testing.T) {
c := seedChain(t, 0)
n := NewNode(c)
status, err := n.Status()
if err != nil {
t.Fatalf("Status: %v", err)
}
if status.Height != 0 {
t.Errorf("height: got %d, want 0", status.Height)
}
}
func TestNode_WaitForStatus_Good(t *testing.T) {
c := seedChain(t, 3)
n := NewNode(c)
cmd := n.WaitForStatus()
if cmd == nil {
t.Fatal("WaitForStatus returned nil cmd")
}
msg := cmd()
status, ok := msg.(NodeStatusMsg)
if !ok {
t.Fatalf("WaitForStatus cmd returned %T, want NodeStatusMsg", msg)
}
if status.Height != 3 {
t.Errorf("height: got %d, want 3", status.Height)
}
}
func TestNode_Tick_Good(t *testing.T) {
c := seedChain(t, 5)
n := NewNode(c)
n.interval = 50 * time.Millisecond
start := time.Now()
msg := n.Tick()()
elapsed := time.Since(start)
status, ok := msg.(NodeStatusMsg)
if !ok {
t.Fatalf("Tick cmd returned %T, want NodeStatusMsg", msg)
}
if status.Height != 5 {
t.Errorf("height: got %d, want 5", status.Height)
}
if elapsed < 40*time.Millisecond {
t.Errorf("Tick returned too quickly (%v), expected at least ~50ms", elapsed)
}
}