237 lines
6.8 KiB
Go
237 lines
6.8 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"btcphrasechecker/bitcoin"
|
|
"btcphrasechecker/types"
|
|
|
|
"github.com/tyler-smith/go-bip39"
|
|
)
|
|
|
|
const (
|
|
testMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
|
)
|
|
|
|
func TestBitcoinWalletAnalysis(t *testing.T) {
|
|
seed := bip39.NewSeed(testMnemonic, "")
|
|
|
|
summary, err := bitcoin.AnalyzeWallet(seed, 20, false)
|
|
if err != nil {
|
|
t.Fatalf("AnalyzeWallet failed: %v", err)
|
|
}
|
|
|
|
// Verify summary structure
|
|
if summary == nil {
|
|
t.Fatal("Summary is nil")
|
|
}
|
|
|
|
t.Logf("Total Balance: %.8f BTC", float64(summary.TotalBalance)/100000000.0)
|
|
t.Logf("Total Received: %.8f BTC", float64(summary.TotalReceived)/100000000.0)
|
|
t.Logf("Total Sent: %.8f BTC", float64(summary.TotalSent)/100000000.0)
|
|
t.Logf("Total Transactions: %d", summary.TotalTxCount)
|
|
t.Logf("Active Addresses: %d", len(summary.ActiveAddresses))
|
|
|
|
// Verify that received - sent = balance
|
|
expectedBalance := summary.TotalReceived - summary.TotalSent
|
|
if summary.TotalBalance != expectedBalance {
|
|
t.Errorf("Balance mismatch: balance=%d, received=%d, sent=%d, expected=%d",
|
|
summary.TotalBalance, summary.TotalReceived, summary.TotalSent, expectedBalance)
|
|
}
|
|
|
|
// Verify active addresses
|
|
for i, addr := range summary.ActiveAddresses {
|
|
if addr.Address == "" {
|
|
t.Errorf("Active address %d has empty address", i)
|
|
}
|
|
if addr.Path == "" {
|
|
t.Errorf("Active address %d has empty path", i)
|
|
}
|
|
if addr.Chain != types.ChainBitcoin {
|
|
t.Errorf("Active address %d has wrong chain: %s", i, addr.Chain)
|
|
}
|
|
|
|
// Verify address-level balance
|
|
addrExpectedBalance := addr.Received - addr.Sent
|
|
if addr.Balance != addrExpectedBalance {
|
|
t.Errorf("Address %s balance mismatch: balance=%d, received=%d, sent=%d",
|
|
addr.Address, addr.Balance, addr.Received, addr.Sent)
|
|
}
|
|
|
|
t.Logf(" Address %d: %s (Path: %s)", i, addr.Address, addr.Path)
|
|
t.Logf(" Balance: %.8f BTC, Received: %.8f BTC, Sent: %.8f BTC, Txs: %d",
|
|
float64(addr.Balance)/100000000.0,
|
|
float64(addr.Received)/100000000.0,
|
|
float64(addr.Sent)/100000000.0,
|
|
addr.TxCount)
|
|
}
|
|
}
|
|
|
|
func TestTransactionHistory(t *testing.T) {
|
|
seed := bip39.NewSeed(testMnemonic, "")
|
|
|
|
summary, err := bitcoin.AnalyzeWallet(seed, 20, false)
|
|
if err != nil {
|
|
t.Fatalf("AnalyzeWallet failed: %v", err)
|
|
}
|
|
|
|
if len(summary.ActiveAddresses) == 0 {
|
|
t.Skip("No active addresses found, skipping transaction history test")
|
|
}
|
|
|
|
err = bitcoin.FetchTransactionHistory(summary)
|
|
if err != nil {
|
|
t.Fatalf("FetchTransactionHistory failed: %v", err)
|
|
}
|
|
|
|
t.Logf("Total transactions in history: %d", len(summary.TransactionHistory))
|
|
t.Logf("Receive transactions: %d", summary.ReceiveTxCount)
|
|
t.Logf("Send transactions: %d", summary.SendTxCount)
|
|
|
|
// Verify transaction history
|
|
var prevTime int64
|
|
for i, tx := range summary.TransactionHistory {
|
|
if tx.TxID == "" {
|
|
t.Errorf("Transaction %d has empty TxID", i)
|
|
}
|
|
|
|
if tx.Time.IsZero() {
|
|
t.Logf("Warning: Transaction %d has zero time (might be unconfirmed)", i)
|
|
}
|
|
|
|
// Verify chronological order
|
|
currentTime := tx.Time.Unix()
|
|
if i > 0 && currentTime < prevTime {
|
|
t.Errorf("Transactions not in chronological order at index %d", i)
|
|
}
|
|
prevTime = currentTime
|
|
|
|
// Verify transaction type
|
|
if tx.Type != "received" && tx.Type != "sent" && tx.Type != "self" {
|
|
t.Errorf("Transaction %d has invalid type: %s", i, tx.Type)
|
|
}
|
|
|
|
if i < 10 { // Log first 10 transactions
|
|
t.Logf(" Tx %d: %s", i, tx.TxID[:16])
|
|
t.Logf(" Date: %s", tx.Time.Format("2006-01-02 15:04:05"))
|
|
t.Logf(" Type: %s, Amount: %.8f BTC, Balance: %.8f BTC",
|
|
tx.Type,
|
|
float64(tx.Amount)/100000000.0,
|
|
float64(tx.Balance)/100000000.0)
|
|
}
|
|
}
|
|
|
|
// Note: Final balance verification is complex due to self-transactions and
|
|
// transaction ordering across multiple addresses. The running balance is calculated
|
|
// per-address chronologically, but may not match the total wallet balance due to
|
|
// timing and internal transfers.
|
|
if len(summary.TransactionHistory) > 0 {
|
|
lastTx := summary.TransactionHistory[len(summary.TransactionHistory)-1]
|
|
t.Logf("Final tx balance: %.8f BTC, Total wallet balance: %.8f BTC",
|
|
float64(lastTx.Balance)/100000000.0,
|
|
float64(summary.TotalBalance)/100000000.0)
|
|
}
|
|
}
|
|
|
|
func TestKnownAddresses(t *testing.T) {
|
|
seed := bip39.NewSeed(testMnemonic, "")
|
|
|
|
addresses, err := bitcoin.DeriveAddresses(seed, 20)
|
|
if err != nil {
|
|
t.Fatalf("DeriveAddresses failed: %v", err)
|
|
}
|
|
|
|
// Map of known addresses for the test mnemonic
|
|
knownAddresses := map[string]string{
|
|
"m/44'/0'/0'/0/0": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA",
|
|
"m/44'/0'/0'/0/1": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP",
|
|
"m/49'/0'/0'/0/0": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf",
|
|
"m/84'/0'/0'/0/0": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu",
|
|
"m/84'/0'/0'/0/1": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g",
|
|
}
|
|
|
|
addressMap := make(map[string]string)
|
|
for _, addr := range addresses {
|
|
addressMap[addr.Path] = addr.Address
|
|
}
|
|
|
|
for path, expectedAddr := range knownAddresses {
|
|
actualAddr, exists := addressMap[path]
|
|
if !exists {
|
|
t.Errorf("Address for path %s not found", path)
|
|
continue
|
|
}
|
|
if actualAddr != expectedAddr {
|
|
t.Errorf("Address mismatch for path %s:\n expected: %s\n got: %s",
|
|
path, expectedAddr, actualAddr)
|
|
} else {
|
|
t.Logf("✓ %s = %s", path, actualAddr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMnemonicValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mnemonic string
|
|
valid bool
|
|
}{
|
|
{
|
|
name: "Valid 12-word mnemonic",
|
|
mnemonic: testMnemonic,
|
|
valid: true,
|
|
},
|
|
{
|
|
name: "Invalid mnemonic",
|
|
mnemonic: "invalid mnemonic phrase that should not work",
|
|
valid: false,
|
|
},
|
|
{
|
|
name: "Empty mnemonic",
|
|
mnemonic: "",
|
|
valid: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
valid := bip39.IsMnemonicValid(tt.mnemonic)
|
|
if valid != tt.valid {
|
|
t.Errorf("Expected mnemonic validity to be %v, got %v", tt.valid, valid)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPassphraseSupport(t *testing.T) {
|
|
// Test that different passphrases generate different seeds
|
|
seed1 := bip39.NewSeed(testMnemonic, "")
|
|
seed2 := bip39.NewSeed(testMnemonic, "password123")
|
|
|
|
if string(seed1) == string(seed2) {
|
|
t.Error("Seeds with different passphrases should be different")
|
|
}
|
|
|
|
// Verify that addresses are different with different passphrases
|
|
addresses1, err := bitcoin.DeriveAddresses(seed1, 1)
|
|
if err != nil {
|
|
t.Fatalf("DeriveAddresses failed for seed1: %v", err)
|
|
}
|
|
|
|
addresses2, err := bitcoin.DeriveAddresses(seed2, 1)
|
|
if err != nil {
|
|
t.Fatalf("DeriveAddresses failed for seed2: %v", err)
|
|
}
|
|
|
|
if len(addresses1) == 0 || len(addresses2) == 0 {
|
|
t.Fatal("No addresses generated")
|
|
}
|
|
|
|
if addresses1[0].Address == addresses2[0].Address {
|
|
t.Error("Addresses should be different with different passphrases")
|
|
}
|
|
|
|
t.Logf("Without passphrase: %s", addresses1[0].Address)
|
|
t.Logf("With passphrase: %s", addresses2[0].Address)
|
|
}
|