Files
zeekayandHanzo Dev 64519916bf the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 13:01:04 -07:00

490 lines
11 KiB
Go

// Copyright (c) 2025 Lux Industries Inc
// SPDX-License-Identifier: MIT
package stats
import (
"context"
"database/sql"
"math/big"
"testing"
"time"
_ "github.com/lib/pq"
)
func TestDefaultConfig(t *testing.T) {
config := DefaultConfig()
if config.CacheTTL != 5*time.Minute {
t.Errorf("CacheTTL = %v, want 5 minutes", config.CacheTTL)
}
if config.GasPriceBlocks != 200 {
t.Errorf("GasPriceBlocks = %d, want 200", config.GasPriceBlocks)
}
if config.SlowPercentile != 35.0 {
t.Errorf("SlowPercentile = %f, want 35.0", config.SlowPercentile)
}
if config.AveragePercentile != 60.0 {
t.Errorf("AveragePercentile = %f, want 60.0", config.AveragePercentile)
}
if config.FastPercentile != 90.0 {
t.Errorf("FastPercentile = %f, want 90.0", config.FastPercentile)
}
if config.SimpleTransactionGas != 21000 {
t.Errorf("SimpleTransactionGas = %d, want 21000", config.SimpleTransactionGas)
}
if config.CounterUpdateInterval != 30*time.Second {
t.Errorf("CounterUpdateInterval = %v, want 30s", config.CounterUpdateInterval)
}
if config.BlockTimeBlocks != 100 {
t.Errorf("BlockTimeBlocks = %d, want 100", config.BlockTimeBlocks)
}
}
func TestWeiToGwei(t *testing.T) {
tests := []struct {
name string
wei *big.Int
expected string
}{
{
name: "nil",
wei: nil,
expected: "0",
},
{
name: "zero",
wei: big.NewInt(0),
expected: "0",
},
{
name: "1 gwei",
wei: big.NewInt(1000000000),
expected: "1.000000",
},
{
name: "10 gwei",
wei: big.NewInt(10000000000),
expected: "10.000000",
},
{
name: "0.5 gwei",
wei: big.NewInt(500000000),
expected: "0.500000",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := weiToGwei(tt.wei)
if result != tt.expected {
t.Errorf("weiToGwei(%v) = %s, want %s", tt.wei, result, tt.expected)
}
})
}
}
func TestWeiToGweiFloat(t *testing.T) {
tests := []struct {
name string
wei *big.Int
expected float64
}{
{
name: "nil",
wei: nil,
expected: 0,
},
{
name: "zero",
wei: big.NewInt(0),
expected: 0,
},
{
name: "1 gwei",
wei: big.NewInt(1000000000),
expected: 1.0,
},
{
name: "100 gwei",
wei: big.NewInt(100000000000),
expected: 100.0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := weiToGweiFloat(tt.wei)
if result != tt.expected {
t.Errorf("weiToGweiFloat(%v) = %f, want %f", tt.wei, result, tt.expected)
}
})
}
}
func TestNormalizeAddress(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "lowercase with prefix",
input: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
{
name: "uppercase prefix",
input: "0X742d35cc6634c0532925a3b844bc9e7595f2bd30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
{
name: "no prefix",
input: "742d35cc6634c0532925a3b844bc9e7595f2bd30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := normalizeAddress(tt.input)
if result != tt.expected {
t.Errorf("normalizeAddress(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestCreateGasPrice(t *testing.T) {
gasPrice := big.NewInt(50000000000) // 50 gwei
baseFee := big.NewInt(30000000000) // 30 gwei
estimatedTime := 2000.0 // 2 seconds
simpleGas := int64(21000)
result := createGasPrice(gasPrice, baseFee, estimatedTime, simpleGas)
if result == nil {
t.Fatal("createGasPrice returned nil")
}
if result.Price != 50.0 {
t.Errorf("Price = %f, want 50.0", result.Price)
}
if result.BaseFee != 30.0 {
t.Errorf("BaseFee = %f, want 30.0", result.BaseFee)
}
if result.PriorityFee != 20.0 {
t.Errorf("PriorityFee = %f, want 20.0", result.PriorityFee)
}
if result.Time != estimatedTime {
t.Errorf("Time = %f, want %f", result.Time, estimatedTime)
}
if result.Wei != "50000000000" {
t.Errorf("Wei = %s, want 50000000000", result.Wei)
}
}
func TestNewCache(t *testing.T) {
ttl := 10 * time.Minute
cache := NewCache(ttl)
if cache == nil {
t.Fatal("NewCache returned nil")
}
if cache.ttl != ttl {
t.Errorf("cache.ttl = %v, want %v", cache.ttl, ttl)
}
if cache.tokenStats == nil {
t.Error("cache.tokenStats should be initialized")
}
}
func TestNewService(t *testing.T) {
config := DefaultConfig()
service := NewService(nil, config)
if service == nil {
t.Fatal("NewService returned nil")
}
if service.cache == nil {
t.Error("service.cache should be initialized")
}
if service.config.GasPriceBlocks != config.GasPriceBlocks {
t.Errorf("service.config not properly set")
}
}
func TestCache_InvalidateCache(t *testing.T) {
config := DefaultConfig()
service := NewService(nil, config)
// Pre-populate cache
service.cache.mu.Lock()
service.cache.gasPrices = &GasPrices{UpdatedAt: time.Now()}
service.cache.counters = &Counters{TotalBlocks: 100}
service.cache.blockTime = &BlockTimeStats{AverageBlockTime: time.Second}
service.cache.tokenStats["0x123"] = &TokenStats{HolderCount: 50}
service.cache.mu.Unlock()
// Invalidate
service.InvalidateCache()
service.cache.mu.RLock()
defer service.cache.mu.RUnlock()
if service.cache.gasPrices != nil {
t.Error("gasPrices should be nil after invalidation")
}
if service.cache.counters != nil {
t.Error("counters should be nil after invalidation")
}
if service.cache.blockTime != nil {
t.Error("blockTime should be nil after invalidation")
}
if len(service.cache.tokenStats) != 0 {
t.Error("tokenStats should be empty after invalidation")
}
}
func setupTestDB(t *testing.T) *sql.DB {
db, err := sql.Open("postgres", "postgres://localhost/explorer_evm_test?sslmode=disable")
if err != nil {
t.Skip("No test database available:", err)
}
if err := db.Ping(); err != nil {
t.Skip("Cannot connect to test database:", err)
}
return db
}
func TestService_Integration(t *testing.T) {
db := setupTestDB(t)
if db == nil {
return
}
defer db.Close()
config := DefaultConfig()
config.CacheTTL = 1 * time.Second // Short TTL for testing
service := NewService(db, config)
ctx := context.Background()
t.Run("GetCounters", func(t *testing.T) {
counters, err := service.GetCounters(ctx)
if err != nil {
t.Fatalf("GetCounters failed: %v", err)
}
if counters == nil {
t.Fatal("GetCounters returned nil")
}
// Values can be 0 for empty test database
if counters.UpdatedAt.IsZero() {
t.Error("UpdatedAt should be set")
}
})
t.Run("GetGasPrices", func(t *testing.T) {
prices, err := service.GetGasPrices(ctx)
if err != nil {
t.Fatalf("GetGasPrices failed: %v", err)
}
if prices == nil {
t.Fatal("GetGasPrices returned nil")
}
if prices.UpdatedAt.IsZero() {
t.Error("UpdatedAt should be set")
}
})
t.Run("GetAverageBlockTime", func(t *testing.T) {
stats, err := service.GetAverageBlockTime(ctx)
if err != nil {
t.Fatalf("GetAverageBlockTime failed: %v", err)
}
if stats == nil {
t.Fatal("GetAverageBlockTime returned nil")
}
if stats.UpdatedAt.IsZero() {
t.Error("UpdatedAt should be set")
}
})
t.Run("GetDailyTransactionStats", func(t *testing.T) {
stats, err := service.GetDailyTransactionStats(ctx, 7)
if err != nil {
t.Fatalf("GetDailyTransactionStats failed: %v", err)
}
// Can be empty for test database
_ = stats
})
t.Run("GetHourlyGasUsage", func(t *testing.T) {
stats, err := service.GetHourlyGasUsage(ctx)
if err != nil {
t.Fatalf("GetHourlyGasUsage failed: %v", err)
}
// Can be empty for test database
_ = stats
})
t.Run("GetAddressStats", func(t *testing.T) {
stats, err := service.GetAddressStats(ctx, "0x0000000000000000000000000000000000000000")
if err != nil {
t.Fatalf("GetAddressStats failed: %v", err)
}
if stats == nil {
t.Fatal("GetAddressStats returned nil")
}
})
t.Run("GetTokenStats", func(t *testing.T) {
stats, err := service.GetTokenStats(ctx, "0x0000000000000000000000000000000000000000")
if err != nil {
t.Fatalf("GetTokenStats failed: %v", err)
}
if stats == nil {
t.Fatal("GetTokenStats returned nil")
}
})
}
func TestGasPrices_Struct(t *testing.T) {
prices := &GasPrices{
Slow: &GasPrice{
Price: 10,
PriorityFee: 2,
BaseFee: 8,
Time: 5000,
},
Average: &GasPrice{
Price: 20,
PriorityFee: 5,
BaseFee: 15,
Time: 3000,
},
Fast: &GasPrice{
Price: 50,
PriorityFee: 20,
BaseFee: 30,
Time: 1000,
},
UpdatedAt: time.Now(),
BaseFee: "30.000000",
}
if prices.Slow.Price >= prices.Average.Price {
t.Error("Slow price should be less than average")
}
if prices.Average.Price >= prices.Fast.Price {
t.Error("Average price should be less than fast")
}
if prices.Slow.Time <= prices.Average.Time {
t.Error("Slow time should be greater than average")
}
if prices.Average.Time <= prices.Fast.Time {
t.Error("Average time should be greater than fast")
}
}
func TestCounters_Struct(t *testing.T) {
counters := &Counters{
TotalBlocks: 1000000,
TotalTransactions: 5000000,
TotalAddresses: 100000,
TotalContracts: 10000,
TotalTokens: 500,
TotalTokenTransfers: 2000000,
AverageBlockTime: 2.0,
TPS24h: 15.5,
GasUsed24h: 1000000000,
UpdatedAt: time.Now(),
}
if counters.TotalContracts > counters.TotalAddresses {
t.Error("Total contracts should not exceed total addresses")
}
if counters.TotalTokens > counters.TotalContracts {
t.Error("Total tokens should not exceed total contracts")
}
}
func TestDailyStats_Struct(t *testing.T) {
stat := DailyStats{
Date: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
Count: 1000,
}
if stat.Date.IsZero() {
t.Error("Date should be set")
}
if stat.Count <= 0 {
t.Error("Count should be positive")
}
}
func TestHourlyStats_Struct(t *testing.T) {
stat := HourlyStats{
Hour: time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC),
Value: 500000,
}
if stat.Hour.IsZero() {
t.Error("Hour should be set")
}
if stat.Value <= 0 {
t.Error("Value should be positive")
}
}
func BenchmarkWeiToGwei(b *testing.B) {
wei := big.NewInt(50000000000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
weiToGwei(wei)
}
}
func BenchmarkWeiToGweiFloat(b *testing.B) {
wei := big.NewInt(50000000000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
weiToGweiFloat(wei)
}
}
func BenchmarkNormalizeAddress(b *testing.B) {
addresses := []string{
"0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
"0X742d35cc6634c0532925a3b844bc9e7595f2bd30",
"742d35cc6634c0532925a3b844bc9e7595f2bd30",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, addr := range addresses {
normalizeAddress(addr)
}
}
}
func BenchmarkCreateGasPrice(b *testing.B) {
gasPrice := big.NewInt(50000000000)
baseFee := big.NewInt(30000000000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
createGasPrice(gasPrice, baseFee, 2000.0, 21000)
}
}