app is now using historical exchange rates for transactions

This commit is contained in:
Matthias Hinrichs
2025-07-05 03:44:53 +02:00
parent a96bbe4d2a
commit aef9342cc5
11 changed files with 926 additions and 237 deletions
+223 -14
View File
@@ -153,13 +153,15 @@ func TestGetCacheInfo(t *testing.T) {
// Add test data to cache
testTime := time.Now()
cacheMutex.Lock()
exchangeRateCache["EUR_USD"] = CachedRate{
exchangeRateCache["EUR_USD_2024-01-01"] = CachedRate{
Rate: 1.2,
Timestamp: testTime,
Date: "2024-01-01",
}
exchangeRateCache["GBP_EUR"] = CachedRate{
exchangeRateCache["GBP_EUR_2024-01-01"] = CachedRate{
Rate: 1.15,
Timestamp: testTime,
Date: "2024-01-01",
}
cacheMutex.Unlock()
@@ -169,14 +171,14 @@ func TestGetCacheInfo(t *testing.T) {
t.Errorf("Expected 2 cache entries, got %d", len(info))
}
if timestamp, exists := info["EUR_USD"]; !exists {
t.Error("Expected EUR_USD entry in cache 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"]; !exists {
t.Error("Expected GBP_EUR entry in cache info")
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")
}
@@ -186,20 +188,227 @@ func TestGetCacheInfo(t *testing.T) {
func TestCacheExpiration(t *testing.T) {
ClearCache()
// Add expired entry to cache
expiredTime := time.Now().Add(-2 * time.Hour) // 2 hours ago
// 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["TEST_EXPIRED"] = CachedRate{
Rate: 1.0,
exchangeRateCache[cacheKey] = CachedRate{
Rate: expiredRate,
Timestamp: expiredTime,
Date: "2024-01-01",
}
cacheMutex.Unlock()
// This should not use the expired cache (but will fail API call)
// We're mainly testing that it doesn't return the cached expired value
_, err := GetExchangeRate("TEST", "EXPIRED")
// 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 {
t.Error("Expected error due to invalid currency pair, but got none")
// 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")
}
}