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

538 lines
11 KiB
Go

// Copyright (c) 2025 Lux Industries Inc
// SPDX-License-Identifier: MIT
package account
import (
"context"
"database/sql"
"testing"
"time"
_ "github.com/lib/pq"
)
func TestDefaultConfig(t *testing.T) {
config := DefaultConfig()
if config.MaxAPIKeysPerUser != 3 {
t.Errorf("MaxAPIKeysPerUser = %d, want 3", config.MaxAPIKeysPerUser)
}
}
func TestNormalizeAddress(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "lowercase with prefix",
input: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
{
name: "uppercase",
input: "0x742D35CC6634C0532925A3B844BC9E7595F2BD30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
{
name: "no prefix",
input: "742d35cc6634c0532925a3b844bc9e7595f2bd30",
expected: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
},
{
name: "mixed case 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 TestHashUID(t *testing.T) {
// Same input should produce same hash
uid := "test-user-123"
hash1 := hashUID(uid)
hash2 := hashUID(uid)
if hash1 != hash2 {
t.Error("Same UID should produce same hash")
}
// Different input should produce different hash
hash3 := hashUID("different-user")
if hash1 == hash3 {
t.Error("Different UIDs should produce different hashes")
}
}
func TestGenerateRandomToken(t *testing.T) {
token1 := generateRandomToken(16)
token2 := generateRandomToken(16)
if len(token1) != 32 { // hex encoding doubles length
t.Errorf("Token length = %d, want 32", len(token1))
}
if token1 == token2 {
t.Error("Random tokens should be different")
}
}
func TestDefaultLimit(t *testing.T) {
limit := DefaultLimit()
if limit.RequestsPerSecond != 10 {
t.Errorf("RequestsPerSecond = %d, want 10", limit.RequestsPerSecond)
}
if limit.RequestsPerDay != 100000 {
t.Errorf("RequestsPerDay = %d, want 100000", limit.RequestsPerDay)
}
if limit.BurstSize != 20 {
t.Errorf("BurstSize = %d, want 20", limit.BurstSize)
}
}
func TestRateLimiter_Allow(t *testing.T) {
limit := &Limit{
RequestsPerSecond: 5,
RequestsPerDay: 1000,
BurstSize: 10,
}
rl := NewRateLimiter(limit)
apiKey := "test-key-1"
// Should allow burst requests
for i := 0; i < 10; i++ {
if !rl.Allow(apiKey) {
t.Errorf("Request %d should be allowed (within burst)", i)
}
}
// Next request should be rate limited
if rl.Allow(apiKey) {
t.Error("Request 11 should be rate limited")
}
}
func TestRateLimiter_SetKeyLimit(t *testing.T) {
rl := NewRateLimiter(DefaultLimit())
apiKey := "custom-limit-key"
customLimit := &Limit{
RequestsPerSecond: 1, // Very slow refill to avoid race conditions
RequestsPerDay: 1000000,
BurstSize: 20,
}
rl.SetKeyLimit(apiKey, customLimit)
// Should allow up to burst size
for i := 0; i < 20; i++ {
if !rl.Allow(apiKey) {
t.Errorf("Request %d should be allowed with custom limit", i)
}
}
// Next should be limited (no time for refill with 1 req/s)
if rl.Allow(apiKey) {
t.Error("Request 21 should be rate limited")
}
}
func TestRateLimiter_GetUsage(t *testing.T) {
limit := &Limit{
RequestsPerSecond: 10,
RequestsPerDay: 1000,
BurstSize: 20,
}
rl := NewRateLimiter(limit)
apiKey := "usage-test-key"
// Initial usage
sec, day := rl.GetUsage(apiKey)
if sec != 10 {
t.Errorf("Initial secondsRemaining = %d, want 10", sec)
}
if day != 1000 {
t.Errorf("Initial dayRemaining = %d, want 1000", day)
}
// Make some requests
rl.Allow(apiKey)
rl.Allow(apiKey)
rl.Allow(apiKey)
sec, day = rl.GetUsage(apiKey)
// Day tokens should decrease
if day != 997 {
t.Errorf("After 3 requests dayRemaining = %d, want 997", day)
}
}
func TestRateLimiter_RemoveKey(t *testing.T) {
rl := NewRateLimiter(DefaultLimit())
apiKey := "remove-test-key"
// Add and use key
rl.Allow(apiKey)
// Remove key
rl.RemoveKey(apiKey)
// Usage should be reset to defaults
sec, day := rl.GetUsage(apiKey)
if sec != 10 {
t.Errorf("After remove secondsRemaining = %d, want 10", sec)
}
if day != 100000 {
t.Errorf("After remove dayRemaining = %d, want 100000", day)
}
}
func TestUser_Struct(t *testing.T) {
user := &User{
ID: 1,
UID: "oauth-uid-123",
Email: "[email protected]",
Name: "Test User",
Nickname: "testuser",
Avatar: "https://example.com/avatar.png",
AddressHash: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
EmailVerified: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if user.ID <= 0 {
t.Error("User ID should be positive")
}
if user.Email == "" {
t.Error("User email should be set")
}
}
func TestAPIKey_Struct(t *testing.T) {
now := time.Now()
key := &APIKey{
Value: "550e8400-e29b-41d4-a716-446655440000",
Name: "My API Key",
UserID: 1,
CreatedAt: now,
UpdatedAt: now,
LastUsedAt: &now,
RequestCount: 100,
}
if len(key.Value) != 36 { // UUID format
t.Errorf("API key value length = %d, want 36", len(key.Value))
}
if key.Name == "" {
t.Error("API key name should be set")
}
}
func TestWatchlist_Struct(t *testing.T) {
watchlist := &Watchlist{
ID: 1,
UserID: 1,
Name: "My Watchlist",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if watchlist.ID <= 0 {
t.Error("Watchlist ID should be positive")
}
if watchlist.Name == "" {
t.Error("Watchlist name should be set")
}
}
func TestWatchlistAddress_Struct(t *testing.T) {
addr := &WatchlistAddress{
ID: 1,
WatchlistID: 1,
AddressHash: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
Name: "My Wallet",
NotifyIncoming: true,
NotifyOutgoing: true,
NotifyERC20: true,
NotifyNFT: true,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if addr.AddressHash == "" {
t.Error("Address hash should be set")
}
if !addr.NotifyIncoming {
t.Error("Notify incoming should be true")
}
}
func TestTagAddress_Struct(t *testing.T) {
tag := &TagAddress{
ID: 1,
UserID: 1,
AddressHash: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
Tag: "Binance Hot Wallet",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if tag.Tag == "" {
t.Error("Tag should be set")
}
}
func TestCustomABI_Struct(t *testing.T) {
abi := &CustomABI{
ID: 1,
UserID: 1,
ContractHash: "0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
Name: "MyToken",
ABI: `[{"type":"function","name":"transfer","inputs":[]}]`,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if abi.ABI == "" {
t.Error("ABI should be set")
}
if abi.Name == "" {
t.Error("Name should be set")
}
}
func TestErrors(t *testing.T) {
// Check error values are unique
errors := []error{
ErrUserNotFound,
ErrInvalidAPIKey,
ErrAPIKeyLimitReached,
ErrWatchlistNotFound,
ErrAddressExists,
ErrRateLimited,
}
for i, e1 := range errors {
for j, e2 := range errors {
if i != j && e1.Error() == e2.Error() {
t.Errorf("Errors should be unique: %v == %v", e1, e2)
}
}
}
}
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()
service := NewService(db, config)
ctx := context.Background()
// Initialize schema
t.Run("InitSchema", func(t *testing.T) {
err := service.InitSchema(ctx)
if err != nil {
t.Fatalf("InitSchema failed: %v", err)
}
})
var testUserID int64
// Create user
t.Run("CreateUser", func(t *testing.T) {
user := &User{
UID: "test-uid-" + generateRandomToken(8),
Email: "[email protected]",
Name: "Test User",
Nickname: "testuser",
EmailVerified: true,
}
err := service.CreateUser(ctx, user)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if user.ID <= 0 {
t.Error("User ID should be set after creation")
}
testUserID = user.ID
})
// Get user by ID
t.Run("GetUserByID", func(t *testing.T) {
if testUserID == 0 {
t.Skip("No user created")
}
user, err := service.GetUserByID(ctx, testUserID)
if err != nil {
t.Fatalf("GetUserByID failed: %v", err)
}
if user.Email != "[email protected]" {
t.Errorf("Email = %s, want [email protected]", user.Email)
}
})
var testAPIKey string
// Create API key
t.Run("CreateAPIKey", func(t *testing.T) {
if testUserID == 0 {
t.Skip("No user created")
}
key, err := service.CreateAPIKey(ctx, testUserID, "Test Key")
if err != nil {
t.Fatalf("CreateAPIKey failed: %v", err)
}
if key.Value == "" {
t.Error("API key value should be set")
}
testAPIKey = key.Value
})
// Get API key
t.Run("GetAPIKey", func(t *testing.T) {
if testAPIKey == "" {
t.Skip("No API key created")
}
key, err := service.GetAPIKey(ctx, testAPIKey)
if err != nil {
t.Fatalf("GetAPIKey failed: %v", err)
}
if key.Name != "Test Key" {
t.Errorf("Name = %s, want Test Key", key.Name)
}
})
// Get user watchlists
t.Run("GetUserWatchlists", func(t *testing.T) {
if testUserID == 0 {
t.Skip("No user created")
}
watchlists, err := service.GetUserWatchlists(ctx, testUserID)
if err != nil {
t.Fatalf("GetUserWatchlists failed: %v", err)
}
if len(watchlists) == 0 {
t.Error("User should have at least one default watchlist")
}
})
// Set address tag
t.Run("SetAddressTag", func(t *testing.T) {
if testUserID == 0 {
t.Skip("No user created")
}
err := service.SetAddressTag(ctx, testUserID, "0x742d35cc6634c0532925a3b844bc9e7595f2bd30", "My Wallet")
if err != nil {
t.Fatalf("SetAddressTag failed: %v", err)
}
tags, err := service.GetUserAddressTags(ctx, testUserID)
if err != nil {
t.Fatalf("GetUserAddressTags failed: %v", err)
}
if len(tags) == 0 {
t.Error("Should have at least one tag")
}
})
// Clean up
t.Run("DeleteUser", func(t *testing.T) {
if testUserID == 0 {
t.Skip("No user created")
}
err := service.DeleteUser(ctx, testUserID)
if err != nil {
t.Fatalf("DeleteUser failed: %v", err)
}
// Verify user is deleted
_, err = service.GetUserByID(ctx, testUserID)
if err != ErrUserNotFound {
t.Error("User should be deleted")
}
})
}
func BenchmarkRateLimiter_Allow(b *testing.B) {
rl := NewRateLimiter(&Limit{
RequestsPerSecond: 1000,
RequestsPerDay: 1000000,
BurstSize: 10000,
})
apiKey := "bench-key"
b.ResetTimer()
for i := 0; i < b.N; i++ {
rl.Allow(apiKey)
}
}
func BenchmarkNormalizeAddress(b *testing.B) {
addresses := []string{
"0x742d35cc6634c0532925a3b844bc9e7595f2bd30",
"742D35CC6634C0532925A3B844BC9E7595F2BD30",
"742d35cc6634c0532925a3b844bc9e7595f2bd30",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, addr := range addresses {
normalizeAddress(addr)
}
}
}
func BenchmarkHashUID(b *testing.B) {
uid := "oauth-user-12345678"
b.ResetTimer()
for i := 0; i < b.N; i++ {
hashUID(uid)
}
}