|
|
@@ -0,0 +1,170 @@
|
|
|
+package currency
|
|
|
+
|
|
|
+import (
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+func TestMemoryCache_SetAndGet(t *testing.T) {
|
|
|
+ cache := newMemoryCache(1 * time.Hour)
|
|
|
+ defer cache.stop()
|
|
|
+
|
|
|
+ // Test setting and getting rates
|
|
|
+ rates := &cachedRates{
|
|
|
+ rates: map[string]float64{
|
|
|
+ "EUR": 0.85,
|
|
|
+ "GBP": 0.73,
|
|
|
+ },
|
|
|
+ baseCode: "USD",
|
|
|
+ fetchedAt: time.Now(),
|
|
|
+ }
|
|
|
+
|
|
|
+ cache.set("USD", rates)
|
|
|
+
|
|
|
+ // Test successful get
|
|
|
+ retrieved, found := cache.get("USD")
|
|
|
+ if !found {
|
|
|
+ t.Error("expected to find cached rates")
|
|
|
+ }
|
|
|
+
|
|
|
+ if retrieved.baseCode != "USD" {
|
|
|
+ t.Errorf("expected base code USD, got %s", retrieved.baseCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(retrieved.rates) != 2 {
|
|
|
+ t.Errorf("expected 2 rates, got %d", len(retrieved.rates))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test non-existent key
|
|
|
+ _, found = cache.get("EUR")
|
|
|
+ if found {
|
|
|
+ t.Error("expected not to find rates for EUR")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMemoryCache_Expiration(t *testing.T) {
|
|
|
+ // Use short TTL for testing
|
|
|
+ cache := newMemoryCache(100 * time.Millisecond)
|
|
|
+ defer cache.stop()
|
|
|
+
|
|
|
+ rates := &cachedRates{
|
|
|
+ rates: map[string]float64{
|
|
|
+ "EUR": 0.85,
|
|
|
+ },
|
|
|
+ baseCode: "USD",
|
|
|
+ fetchedAt: time.Now(),
|
|
|
+ }
|
|
|
+
|
|
|
+ cache.set("USD", rates)
|
|
|
+
|
|
|
+ // Should find it immediately
|
|
|
+ _, found := cache.get("USD")
|
|
|
+ if !found {
|
|
|
+ t.Error("expected to find cached rates immediately")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait for expiration
|
|
|
+ time.Sleep(150 * time.Millisecond)
|
|
|
+
|
|
|
+ // Should not find it after expiration
|
|
|
+ _, found = cache.get("USD")
|
|
|
+ if found {
|
|
|
+ t.Error("expected rates to be expired")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMemoryCache_Clear(t *testing.T) {
|
|
|
+ cache := newMemoryCache(1 * time.Hour)
|
|
|
+ defer cache.stop()
|
|
|
+
|
|
|
+ // Add multiple entries
|
|
|
+ for _, base := range []string{"USD", "EUR", "GBP"} {
|
|
|
+ rates := &cachedRates{
|
|
|
+ rates: map[string]float64{"TEST": 1.0},
|
|
|
+ baseCode: base,
|
|
|
+ fetchedAt: time.Now(),
|
|
|
+ }
|
|
|
+ cache.set(base, rates)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify all entries exist
|
|
|
+ for _, base := range []string{"USD", "EUR", "GBP"} {
|
|
|
+ _, found := cache.get(base)
|
|
|
+ if !found {
|
|
|
+ t.Errorf("expected to find rates for %s", base)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Clear cache
|
|
|
+ cache.clear()
|
|
|
+
|
|
|
+ // Verify all entries are gone
|
|
|
+ for _, base := range []string{"USD", "EUR", "GBP"} {
|
|
|
+ _, found := cache.get(base)
|
|
|
+ if found {
|
|
|
+ t.Errorf("expected not to find rates for %s after clear", base)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMemoryCache_Stats(t *testing.T) {
|
|
|
+ cache := newMemoryCache(1 * time.Hour)
|
|
|
+ defer cache.stop()
|
|
|
+
|
|
|
+ // Initially empty
|
|
|
+ entries, _ := cache.stats()
|
|
|
+ if entries != 0 {
|
|
|
+ t.Errorf("expected 0 entries, got %d", entries)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add entries
|
|
|
+ now := time.Now()
|
|
|
+ for i, base := range []string{"USD", "EUR", "GBP"} {
|
|
|
+ rates := &cachedRates{
|
|
|
+ rates: map[string]float64{"TEST": 1.0},
|
|
|
+ baseCode: base,
|
|
|
+ fetchedAt: now.Add(time.Duration(i) * time.Minute),
|
|
|
+ }
|
|
|
+ cache.set(base, rates)
|
|
|
+ }
|
|
|
+
|
|
|
+ entries, oldest := cache.stats()
|
|
|
+ if entries != 3 {
|
|
|
+ t.Errorf("expected 3 entries, got %d", entries)
|
|
|
+ }
|
|
|
+
|
|
|
+ // The oldest should be the first one we added (USD)
|
|
|
+ if !oldest.Equal(now) {
|
|
|
+ t.Errorf("expected oldest entry to be %v, got %v", now, oldest)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMemoryCache_Cleanup(t *testing.T) {
|
|
|
+ // Use very short TTL for testing
|
|
|
+ cache := newMemoryCache(50 * time.Millisecond)
|
|
|
+ defer cache.stop()
|
|
|
+
|
|
|
+ // Add entry
|
|
|
+ rates := &cachedRates{
|
|
|
+ rates: map[string]float64{"EUR": 0.85},
|
|
|
+ baseCode: "USD",
|
|
|
+ fetchedAt: time.Now(),
|
|
|
+ }
|
|
|
+ cache.set("USD", rates)
|
|
|
+
|
|
|
+ // Verify it exists
|
|
|
+ entries, _ := cache.stats()
|
|
|
+ if entries != 1 {
|
|
|
+ t.Errorf("expected 1 entry, got %d", entries)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait for cleanup cycle (janitor runs every TTL/2 = 25ms)
|
|
|
+ // Wait a bit longer to ensure cleanup has run
|
|
|
+ time.Sleep(100 * time.Millisecond)
|
|
|
+
|
|
|
+ // Verify it's been cleaned up
|
|
|
+ entries, _ = cache.stats()
|
|
|
+ if entries != 0 {
|
|
|
+ t.Errorf("expected 0 entries after cleanup, got %d", entries)
|
|
|
+ }
|
|
|
+}
|