package imageproxy import ( "bytes" "os" "testing" "time" "sneak.berlin/go/pixa/internal/imagerequest" "sneak.berlin/go/pixa/internal/imgcache" ) // TestProxy_Get_MakesAgainVariantOfNoOutputFormat replaces a cached // variant's file with text, and checks that the next request is a miss // answered with the variant made again, and the one after a hit. func TestProxy_Get_MakesAgainVariantOfNoOutputFormat(t *testing.T) { t.Parallel() svc, fixtures := setupTestProxy(t) req := photoVariant(fixtures, 85, imagerequest.FitCover) want, _ := getImage(t, svc, req) path := variantFilePath(svc, imagerequest.CacheKey(req)) err := os.WriteFile(path, []byte("this is text, not an image"), imgcache.StorageFilePerm) if err != nil { t.Fatalf("replacing the variant file: %v", err) } got, status := getImage(t, svc, req) t.Logf("after the replacement: %d bytes, %s", len(got), status) if status != CacheMiss || !bytes.Equal(got, want) { t.Errorf("after the replacement: Get() = %d bytes, %s; want the %d bytes "+ "made again, %s", len(got), status, len(want), CacheMiss) } got, status = getImage(t, svc, req) if status != CacheHit || !bytes.Equal(got, want) { t.Errorf("next request: Get() = %d bytes, %s; want the %d bytes, %s", len(got), status, len(want), CacheHit) } } // TestProxy_Get_HitWaitsForNoDatabaseWrite holds the database's one // connection while a cached variant is requested. The hit must be answered // while the connection is still held, and its access time must reach the // database once it is free. func TestProxy_Get_HitWaitsForNoDatabaseWrite(t *testing.T) { t.Parallel() svc, fixtures := setupTestProxy(t) req := photoVariant(fixtures, 85, imagerequest.FitCover) key := imagerequest.CacheKey(req) getImage(t, svc, req) // Each connection to an in-memory SQLite database opens a new, empty one, // so the goroutine that writes the access times must share the // connection that holds the schema. svc.db.SetMaxOpenConns(1) svc.cache.StartPendingAccessTimeWrites() defer func() { _ = svc.cache.StopPendingAccessTimeWrites(t.Context()) }() anHourAgo := time.Now().Add(-time.Hour) setVariantLastAccessed(t, svc.db, key, anHourAgo) releaseDatabase := holdDatabase(t, svc.db) defer releaseDatabase() results := startGet(t.Context(), svc, req) select { case got := <-results: if got.err != nil { t.Fatalf("Get() of the cached variant error = %v", got.err) } case <-time.After(pendingAccessTimesWait): t.Fatal("the cache hit waited for the database") } releaseDatabase() deadline := time.Now().Add(pendingAccessTimesWait) for countRows(t, svc.db, ` SELECT COUNT(*) FROM variant_content WHERE cache_key = ? AND last_accessed_at > ? `, string(key), anHourAgo.UTC().Format(sqliteTimestampFormat)) == 0 { if time.Now().After(deadline) { t.Fatal("the hit's access time was not written") } time.Sleep(10 * time.Millisecond) } }