package imgcache import ( "context" "database/sql" "os" "testing" "time" _ "modernc.org/sqlite" ) func setupTestDB(t *testing.T) *sql.DB { t.Helper() db, err := sql.Open("sqlite", ":memory:") if err != nil { t.Fatalf("failed to open test db: %v", err) } // Create schema schema := ` CREATE TABLE source_content ( source_key TEXT PRIMARY KEY, source_host TEXT NOT NULL, source_path TEXT NOT NULL, source_query TEXT NOT NULL DEFAULT '', content_type TEXT NOT NULL, size_bytes INTEGER NOT NULL, fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE output_content ( content_hash TEXT PRIMARY KEY, content_type TEXT NOT NULL, size_bytes INTEGER NOT NULL, fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE request_cache ( id INTEGER PRIMARY KEY AUTOINCREMENT, cache_key TEXT NOT NULL UNIQUE, source_metadata_id INTEGER NOT NULL, output_hash TEXT NOT NULL, width INTEGER NOT NULL, height INTEGER NOT NULL, format TEXT NOT NULL, quality INTEGER NOT NULL DEFAULT 85, fit_mode TEXT NOT NULL DEFAULT 'cover', fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP, access_count INTEGER NOT NULL DEFAULT 1 ); CREATE TABLE negative_cache ( id INTEGER PRIMARY KEY AUTOINCREMENT, source_host TEXT NOT NULL, source_path TEXT NOT NULL, source_query TEXT NOT NULL DEFAULT '', status_code INTEGER NOT NULL, error_message TEXT, fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, UNIQUE(source_host, source_path, source_query) ); CREATE TABLE cache_stats ( id INTEGER PRIMARY KEY CHECK (id = 1), hit_count INTEGER NOT NULL DEFAULT 0, miss_count INTEGER NOT NULL DEFAULT 0, upstream_fetch_count INTEGER NOT NULL DEFAULT 0, upstream_fetch_bytes INTEGER NOT NULL DEFAULT 0, transform_count INTEGER NOT NULL DEFAULT 0, last_updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ); INSERT INTO cache_stats (id) VALUES (1); ` _, err = db.ExecContext(t.Context(), schema) if err != nil { t.Fatalf("failed to create schema: %v", err) } return db } func setupTestCache(t *testing.T) *Cache { t.Helper() tmpDir := t.TempDir() db := setupTestDB(t) cache, err := NewCache(db, CacheConfig{ StateDir: tmpDir, NegativeTTL: 5 * time.Minute, }) if err != nil { t.Fatalf("failed to create cache: %v", err) } return cache } func TestCache_Stats(t *testing.T) { t.Parallel() cache := setupTestCache(t) ctx := context.Background() // Increment some stats cache.IncrementStats(true, 0) cache.IncrementStats(true, 0) cache.IncrementStats(false, 1024) stats, err := cache.Stats(ctx) if err != nil { t.Fatalf("Stats() error = %v", err) } if stats.HitCount != 2 { t.Errorf("Stats() HitCount = %d, want 2", stats.HitCount) } if stats.MissCount != 1 { t.Errorf("Stats() MissCount = %d, want 1", stats.MissCount) } } func TestCache_CleanExpired(t *testing.T) { t.Parallel() tmpDir := t.TempDir() db := setupTestDB(t) cache, _ := NewCache(db, CacheConfig{ StateDir: tmpDir, NegativeTTL: 1 * time.Millisecond, }) ctx := context.Background() // Insert expired negative cache entry directly _, err := db.ExecContext(ctx, ` INSERT INTO negative_cache (source_host, source_path, source_query, status_code, expires_at) VALUES ('example.com', '/old.jpg', '', 404, datetime('now', '-1 hour')) `) if err != nil { t.Fatalf("failed to insert test data: %v", err) } // Verify it exists var count int err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM negative_cache`).Scan(&count) if err != nil { t.Fatalf("failed to count negative cache entries: %v", err) } if count != 1 { t.Fatalf("expected 1 negative cache entry, got %d", count) } // Clean expired err = cache.CleanExpired(ctx) if err != nil { t.Fatalf("CleanExpired() error = %v", err) } // Verify it's gone err = db.QueryRowContext(ctx, `SELECT COUNT(*) FROM negative_cache`).Scan(&count) if err != nil { t.Fatalf("failed to count negative cache entries: %v", err) } if count != 0 { t.Errorf("expected 0 negative cache entries after clean, got %d", count) } } func TestCache_StorageDirectoriesCreated(t *testing.T) { t.Parallel() tmpDir := t.TempDir() db := setupTestDB(t) _, err := NewCache(db, CacheConfig{ StateDir: tmpDir, }) if err != nil { t.Fatalf("NewCache() error = %v", err) } // Verify directories were created dirs := []string{ "cache/sources", "cache/variants", } for _, dir := range dirs { path := tmpDir + "/" + dir _, err := os.Stat(path) if os.IsNotExist(err) { t.Errorf("directory %s was not created", dir) } } }