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) }