package util import ( "testing" "time" ) func TestGetExchangeRate_SameCurrency(t *testing.T) { rate, err := GetExchangeRate("USD", "USD") if err != nil { t.Errorf("Expected no error for same currency, got %v", err) } if rate != 1.0 { t.Errorf("Expected rate 1.0 for same currency, got %f", rate) } } func TestConvertCurrency_SameCurrency(t *testing.T) { amount := 100.0 converted, err := ConvertCurrency(amount, "EUR", "EUR") if err != nil { t.Errorf("Expected no error for same currency conversion, got %v", err) } if converted != amount { t.Errorf("Expected %f for same currency conversion, got %f", amount, converted) } } func TestFormatCurrencyAmount(t *testing.T) { tests := []struct { amount float64 currency string expected string }{ {100.5, "USD", "100.50 USD"}, {1234.567, "EUR", "1234.57 EUR"}, {0.0, "GBP", "0.00 GBP"}, } for _, test := range tests { result := FormatCurrencyAmount(test.amount, test.currency) if result != test.expected { t.Errorf("FormatCurrencyAmount(%.2f, %s) = %s, expected %s", test.amount, test.currency, result, test.expected) } } } func TestGetCurrencySymbol(t *testing.T) { tests := []struct { currency string expected string }{ {"USD", "$"}, {"EUR", "€"}, {"GBP", "£"}, {"JPY", "¥"}, {"CHF", "CHF"}, {"UNKNOWN", "UNKNOWN"}, // Should return currency code if symbol not found } for _, test := range tests { result := GetCurrencySymbol(test.currency) if result != test.expected { t.Errorf("GetCurrencySymbol(%s) = %s, expected %s", test.currency, result, test.expected) } } } func TestFormatCurrencyWithSymbol(t *testing.T) { tests := []struct { amount float64 currency string expected string }{ {100.5, "USD", "$100.50"}, {1234.56, "EUR", "1234.56 €"}, {999.99, "GBP", "£999.99"}, {500.0, "UNKNOWN", "500.00 UNKNOWN"}, } for _, test := range tests { result := FormatCurrencyWithSymbol(test.amount, test.currency) if result != test.expected { t.Errorf("FormatCurrencyWithSymbol(%.2f, %s) = %s, expected %s", test.amount, test.currency, result, test.expected) } } } func TestConvertAndFormat_SameCurrency(t *testing.T) { amount := 100.0 currency := "USD" result := ConvertAndFormat(amount, currency, currency) expected := FormatCurrencyWithSymbol(amount, currency) if result != expected { t.Errorf("ConvertAndFormat(%f, %s, %s) = %s, expected %s", amount, currency, currency, result, expected) } } func TestBatchConvertCurrency_SameCurrency(t *testing.T) { amounts := []float64{100.0, 200.0, 300.0} currency := "EUR" converted, err := BatchConvertCurrency(amounts, currency, currency) if err != nil { t.Errorf("Expected no error for same currency batch conversion, got %v", err) } if len(converted) != len(amounts) { t.Errorf("Expected %d converted amounts, got %d", len(amounts), len(converted)) } for i, amount := range amounts { if converted[i] != amount { t.Errorf("Expected converted[%d] = %f, got %f", i, amount, converted[i]) } } } func TestClearCache(t *testing.T) { // Add something to cache first cacheMutex.Lock() exchangeRateCache["TEST_USD"] = CachedRate{ Rate: 1.5, Timestamp: time.Now(), } cacheMutex.Unlock() // Verify cache has content info := GetCacheInfo() if len(info) == 0 { t.Error("Expected cache to have content before clearing") } // Clear cache ClearCache() // Verify cache is empty info = GetCacheInfo() if len(info) != 0 { t.Errorf("Expected empty cache after clearing, got %d items", len(info)) } } func TestGetCacheInfo(t *testing.T) { // Clear cache first ClearCache() // Add test data to cache testTime := time.Now() cacheMutex.Lock() exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{ Rate: 1.2, Timestamp: testTime, Date: "2024-01-01", } exchangeRateCache["GBP_EUR_2024-01-01"] = CachedRate{ Rate: 1.15, Timestamp: testTime, Date: "2024-01-01", } cacheMutex.Unlock() info := GetCacheInfo() if len(info) != 2 { t.Errorf("Expected 2 cache entries, got %d", len(info)) } if timestamp, exists := info["EUR_USD_2024-01-01"]; !exists { t.Error("Expected EUR_USD_2024-01-01 entry in cache info") } else if !timestamp.Equal(testTime) { t.Error("Expected timestamp to match test time") } if timestamp, exists := info["GBP_EUR_2024-01-01"]; !exists { t.Error("Expected GBP_EUR_2024-01-01 entry in cache info") } else if !timestamp.Equal(testTime) { t.Error("Expected timestamp to match test time") } } // Test cache expiration logic func TestCacheExpiration(t *testing.T) { ClearCache() // Add expired entry to cache with a specific rate expiredTime := time.Now().Add(-25 * time.Hour) // 25 hours ago (older than 24h cache) testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) cacheKey := "USD_EUR_2024-01-01" expiredRate := 999.999 // Unrealistic rate to detect if cache is used cacheMutex.Lock() exchangeRateCache[cacheKey] = CachedRate{ Rate: expiredRate, Timestamp: expiredTime, Date: "2024-01-01", } cacheMutex.Unlock() // Verify the expired entry exists cacheMutex.RLock() _, exists := exchangeRateCache[cacheKey] cacheMutex.RUnlock() if !exists { t.Error("Expected expired cache entry to exist before test") return } // This should not use the expired cache, it should try to fetch from API // Even if API fails, it should not return the expired cached rate rate, err := GetHistoricalExchangeRate("USD", "EUR", testDate) // Either: // 1. We get a reasonable rate from API (not the cached unrealistic rate) // 2. We get an error (which is fine, means cache wasn't used) if err == nil { // If we got a rate, it should not be the expired cached rate if rate == expiredRate { t.Errorf("Function returned expired cached rate %f, should have fetched from API", expiredRate) } // A reasonable EUR/USD rate should be between 0.5 and 2.0 if rate < 0.5 || rate > 2.0 { t.Errorf("Got unreasonable exchange rate %f, might be using expired cache", rate) } } // If err != nil, that's fine - it means the API call was attempted and failed // The important thing is that the expired cache was not used } // Test historical exchange rate functionality func TestGetHistoricalExchangeRate_SameCurrency(t *testing.T) { testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) rate, err := GetHistoricalExchangeRate("USD", "USD", testDate) if err != nil { t.Errorf("Expected no error for same currency, got %v", err) } if rate != 1.0 { t.Errorf("Expected rate 1.0 for same currency, got %f", rate) } } func TestConvertCurrencyHistorical_SameCurrency(t *testing.T) { amount := 100.0 testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) converted, err := ConvertCurrencyHistorical(amount, "EUR", "EUR", testDate) if err != nil { t.Errorf("Expected no error for same currency conversion, got %v", err) } if converted != amount { t.Errorf("Expected %f for same currency conversion, got %f", amount, converted) } } func TestBatchConvertCurrencyHistorical_SameCurrency(t *testing.T) { amounts := []float64{100.0, 200.0, 300.0} currency := "EUR" testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) converted, err := BatchConvertCurrencyHistorical(amounts, currency, currency, testDate) if err != nil { t.Errorf("Expected no error for same currency batch conversion, got %v", err) } if len(converted) != len(amounts) { t.Errorf("Expected %d converted amounts, got %d", len(amounts), len(converted)) } for i, amount := range amounts { if converted[i] != amount { t.Errorf("Expected converted[%d] = %f, got %f", i, amount, converted[i]) } } } func TestConvertAndFormatHistorical_SameCurrency(t *testing.T) { amount := 100.0 currency := "USD" testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) result := ConvertAndFormatHistorical(amount, currency, currency, testDate) expected := FormatCurrencyWithSymbol(amount, currency) if result != expected { t.Errorf("ConvertAndFormatHistorical(%f, %s, %s, %v) = %s, expected %s", amount, currency, currency, testDate, result, expected) } } func TestGetExchangeRateWithFallback_SameCurrency(t *testing.T) { testDate := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) rate, isHistorical, err := GetExchangeRateWithFallback("USD", "USD", testDate) if err != nil { t.Errorf("Expected no error for same currency, got %v", err) } if rate != 1.0 { t.Errorf("Expected rate 1.0 for same currency, got %f", rate) } if !isHistorical { t.Error("Expected isHistorical to be true for same currency") } } func TestCleanExpiredCache(t *testing.T) { // Clear cache first ClearCache() // Add some test data to cache now := time.Now() expiredTime := now.Add(-25 * time.Hour) // Older than 24 hours recentTime := now.Add(-30 * time.Minute) // Recent cacheMutex.Lock() exchangeRateCache["OLD_RATE_2024-01-01"] = CachedRate{ Rate: 1.0, Timestamp: expiredTime, Date: "2024-01-01", } exchangeRateCache["NEW_RATE_"+now.Format("2006-01-02")] = CachedRate{ Rate: 1.1, Timestamp: recentTime, Date: now.Format("2006-01-02"), } cacheMutex.Unlock() // Clean expired cache removed := CleanExpiredCache() // Should have removed the old entry if removed != 1 { t.Errorf("Expected 1 removed entry, got %d", removed) } // Verify the recent entry is still there info := GetCacheInfo() if len(info) != 1 { t.Errorf("Expected 1 remaining cache entry, got %d", len(info)) } } func TestGetDetailedCacheInfo(t *testing.T) { // Clear cache first ClearCache() // Add test data to cache testTime := time.Now() cacheMutex.Lock() exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{ Rate: 1.2, Timestamp: testTime, Date: "2024-01-01", } cacheMutex.Unlock() info := GetDetailedCacheInfo() if len(info) != 1 { t.Errorf("Expected 1 cache entry, got %d", len(info)) } if cached, exists := info["EUR_USD_2024-01-01"]; !exists { t.Error("Expected EUR_USD_2024-01-01 entry in detailed cache info") } else { if cached.Rate != 1.2 { t.Errorf("Expected rate 1.2, got %f", cached.Rate) } if cached.Date != "2024-01-01" { t.Errorf("Expected date 2024-01-01, got %s", cached.Date) } if !cached.Timestamp.Equal(testTime) { t.Error("Expected timestamp to match test time") } } } func TestIsToday(t *testing.T) { now := time.Now() yesterday := now.AddDate(0, 0, -1) tomorrow := now.AddDate(0, 0, 1) if !isToday(now) { t.Error("Expected isToday(now) to be true") } if isToday(yesterday) { t.Error("Expected isToday(yesterday) to be false") } if isToday(tomorrow) { t.Error("Expected isToday(tomorrow) to be false") } } func TestParseDate(t *testing.T) { testDate := "2024-01-01" expected := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) result := parseDate(testDate) if !result.Equal(expected) { t.Errorf("Expected parsed date %v, got %v", expected, result) } // Test invalid date invalidResult := parseDate("invalid-date") if !invalidResult.IsZero() { t.Error("Expected zero time for invalid date") } } // Benchmark tests func BenchmarkGetCurrencySymbol(b *testing.B) { currencies := []string{"USD", "EUR", "GBP", "JPY", "CHF", "UNKNOWN"} for i := 0; i < b.N; i++ { currency := currencies[i%len(currencies)] GetCurrencySymbol(currency) } } func BenchmarkFormatCurrencyWithSymbol(b *testing.B) { for i := 0; i < b.N; i++ { FormatCurrencyWithSymbol(1234.56, "USD") } }