check / check (push) Waiting to run
The new package internal/imageproxy holds Proxy, which was imgcache.Service, with its Config, ImageResponse, CacheStatus and errors. internal/imgcache holds the cache only, and imgcache.CacheConfig is imgcache.Config. The cache reads and writes each kind it keeps with one pair of methods: Variant and PutVariant, Source and PutSource, Failure and PutFailure. Source replaces LookupSource followed by GetSourceContent. imgcache.go and module.go are gone. The proxy's tests moved with it. They reach the cache through its database, its state directory and its metrics registry, and run its reconciliation pass with a second cache, as pixad does at startup. Model: opus-5-5
129 lines
2.7 KiB
Go
129 lines
2.7 KiB
Go
package imgcache
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
_ "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,
|
|
size_bytes INTEGER NOT NULL,
|
|
last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
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 '',
|
|
expires_at DATETIME NOT NULL,
|
|
UNIQUE(source_host, source_path, source_query)
|
|
);
|
|
`
|
|
|
|
_, err = db.ExecContext(t.Context(), schema)
|
|
if err != nil {
|
|
t.Fatalf("failed to create schema: %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func TestCache_CleanExpired(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
db := setupTestDB(t)
|
|
|
|
cache, _ := NewCache(db, Config{
|
|
StateDir: tmpDir,
|
|
NegativeTTL: 1 * time.Millisecond,
|
|
MaxBytes: 1 << 30,
|
|
}, prometheus.NewRegistry())
|
|
|
|
ctx := context.Background()
|
|
|
|
// Insert expired negative cache entry directly
|
|
_, err := db.ExecContext(ctx, `
|
|
INSERT INTO negative_cache
|
|
(source_host, source_path, source_query, expires_at)
|
|
VALUES ('example.com', '/old.jpg', '', 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, Config{
|
|
StateDir: tmpDir,
|
|
MaxBytes: 1 << 30,
|
|
}, prometheus.NewRegistry())
|
|
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)
|
|
}
|
|
}
|
|
}
|