Files
pixa/internal/imgcache/evict_not_checked_recently_internal_test.go
T
clawbot 880845f38c
check / check (push) Canceled after 0s
Check cached sources with upstream after the cache TTL (closes #69)
A cached image was served until eviction, so a change at the same URL
was never picked up.

After 24 hours a request checks the source with upstream, sending its
stored ETag and Last-Modified. A 304 renews it with no download or
conversion. New bytes replace it, and older variants are converted
again. A failed check serves the cached image and puts the URL in the
negative cache. Images not checked for 30 days are evicted.

Variant rows keep the source's ETag and Last-Modified, so a variant
whose source was evicted is checked the same way.

A variant file's modification time is its source's check time when it
was made or renewed, so a hit reads no database row.

Model: opus-5-5
2026-11-11 10:38:51 +00:00

89 lines
2.8 KiB
Go

package imgcache
import (
"bytes"
"os"
"slices"
"testing"
"time"
"sneak.berlin/go/pixa/internal/imagerequest"
)
// TestEvictNotCheckedRecently stores a source and a variant last checked with
// upstream 31 days ago and a source and a variant checked 29 days ago.
// EvictNotCheckedRecently must remove the rows and files of the first two and
// keep the other two.
func TestEvictNotCheckedRecently(t *testing.T) {
t.Parallel()
cache, _ := newEvictionTestCache(t, 1<<30)
old := time.Now().Add(-31 * 24 * time.Hour)
recent := time.Now().Add(-29 * 24 * time.Hour)
oldSource := imagerequest.Source{Host: testHostCDN, Path: "/old.jpg"}
recentSource := imagerequest.Source{Host: testHostCDN, Path: "/recent.jpg"}
for src, checkedAt := range map[imagerequest.Source]time.Time{
oldSource: old, recentSource: recent,
} {
_, err := cache.PutSource(t.Context(), src, bytes.NewReader([]byte("source")),
SourceInfo{FetchedAt: checkedAt, CheckedAt: checkedAt})
if err != nil {
t.Fatalf("PutSource(%s) error = %v", src.Path, err)
}
}
oldVariant := imagerequest.VariantKey("aabbccdd0001")
recentVariant := imagerequest.VariantKey("aabbccdd0002")
for key, checkedAt := range map[imagerequest.VariantKey]time.Time{
oldVariant: old, recentVariant: recent,
} {
err := cache.PutVariant(t.Context(), key, SourceInfo{CheckedAt: checkedAt},
bytes.NewReader([]byte("variant")))
if err != nil {
t.Fatalf("PutVariant(%s) error = %v", key, err)
}
}
err := cache.EvictNotCheckedRecently(t.Context())
if err != nil {
t.Fatalf("EvictNotCheckedRecently() error = %v", err)
}
sourceRows, err := queryStringColumn[imagerequest.SourceKey](t.Context(),
cache.db, `SELECT source_key FROM source_content`, "sources", "source")
if err != nil {
t.Fatal(err)
}
variantRows, err := queryStringColumn[imagerequest.VariantKey](t.Context(),
cache.db, `SELECT cache_key FROM variant_content`, "variants", "variant")
if err != nil {
t.Fatal(err)
}
t.Logf("source rows left = %v, variant rows left = %v", sourceRows, variantRows)
if !slices.Equal(sourceRows, []imagerequest.SourceKey{recentSource.Key()}) {
t.Errorf("source rows left = %v, want only %s", sourceRows, recentSource.Key())
}
if !slices.Equal(variantRows, []imagerequest.VariantKey{recentVariant}) {
t.Errorf("variant rows left = %v, want only %s", variantRows, recentVariant)
}
for path, wantKept := range map[string]bool{
cache.srcContent.keyToPath(string(oldSource.Key())): false,
cache.srcContent.keyToPath(string(recentSource.Key())): true,
cache.variants.keyToPath(string(oldVariant)): false,
cache.variants.keyToPath(string(recentVariant)): true,
} {
_, statErr := os.Stat(path)
if kept := statErr == nil; kept != wantKept {
t.Errorf("file %s kept = %t, want %t (%v)", path, kept, wantKept, statErr)
}
}
}