check / check (push) Canceled after 0s
A source is now one source_content row and one file named by its SourceKey, the SHA-256 of its host, path and query, made as the variant key is. A store writes the file, then upserts the row, and removes the file when the row cannot be written; an eviction deletes the row, then the file, with no lock. source_metadata, the JSON files under cache/metadata/, MetadataStorage, the content lock, the eviction transaction and its test hook are gone, as is the reconciliation pass that dropped a source row whose file is missing. Two source URLs serving the same bytes store them twice. Model: opus-5-5
265 lines
6.2 KiB
Go
265 lines
6.2 KiB
Go
package imgcache
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/pixa/internal/imagerequest"
|
|
)
|
|
|
|
func TestContentStorage_StoreAndLoad(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
storage, err := NewContentStorage(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("NewContentStorage() error = %v", err)
|
|
}
|
|
|
|
content := []byte("hello world")
|
|
key := imagerequest.Source{Host: testHostCDN, Path: testPathCat}.Key()
|
|
|
|
size, err := storage.Store(key, content)
|
|
if err != nil {
|
|
t.Fatalf("Store() error = %v", err)
|
|
}
|
|
|
|
if size != int64(len(content)) {
|
|
t.Errorf("Store() size = %d, want %d", size, len(content))
|
|
}
|
|
|
|
// Verify file exists at expected path
|
|
keyStr := string(key)
|
|
|
|
expectedPath := filepath.Join(tmpDir, keyStr[0:2], keyStr[2:4], keyStr)
|
|
|
|
_, err = os.Stat(expectedPath)
|
|
if err != nil {
|
|
t.Errorf("File not at expected path %s: %v", expectedPath, err)
|
|
}
|
|
|
|
// Load and verify content
|
|
r, err := storage.Load(key)
|
|
if err != nil {
|
|
t.Fatalf("Load() error = %v", err)
|
|
}
|
|
|
|
defer func() { _ = r.Close() }()
|
|
|
|
loaded, err := io.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatalf("ReadAll() error = %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(loaded, content) {
|
|
t.Errorf("Load() content = %q, want %q", loaded, content)
|
|
}
|
|
}
|
|
|
|
func TestContentStorage_LoadNotFound(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
storage, err := NewContentStorage(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("NewContentStorage() error = %v", err)
|
|
}
|
|
|
|
_, err = storage.Load(imagerequest.SourceKey("nonexistent"))
|
|
if !errors.Is(err, ErrNotFound) {
|
|
t.Errorf("Load() error = %v, want ErrNotFound", err)
|
|
}
|
|
}
|
|
|
|
func TestContentStorage_Delete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
storage, err := NewContentStorage(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("NewContentStorage() error = %v", err)
|
|
}
|
|
|
|
content := []byte("to be deleted")
|
|
key := imagerequest.Source{Host: testHostCDN, Path: testPathCat}.Key()
|
|
|
|
_, err = storage.Store(key, content)
|
|
if err != nil {
|
|
t.Fatalf("Store() error = %v", err)
|
|
}
|
|
|
|
if !storage.Exists(key) {
|
|
t.Error("Exists() = false, want true")
|
|
}
|
|
|
|
err = storage.Delete(key)
|
|
if err != nil {
|
|
t.Fatalf("Delete() error = %v", err)
|
|
}
|
|
|
|
if storage.Exists(key) {
|
|
t.Error("Exists() = true after delete, want false")
|
|
}
|
|
}
|
|
|
|
func TestContentStorage_DeleteNonexistent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
storage, err := NewContentStorage(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("NewContentStorage() error = %v", err)
|
|
}
|
|
|
|
// Should not error
|
|
err = storage.Delete(imagerequest.SourceKey("nonexistent"))
|
|
if err != nil {
|
|
t.Errorf("Delete() error = %v, want nil", err)
|
|
}
|
|
}
|
|
|
|
func TestContentStorage_KeyToPath(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
storage, err := NewContentStorage(tmpDir)
|
|
if err != nil {
|
|
t.Fatalf("NewContentStorage() error = %v", err)
|
|
}
|
|
|
|
// Test by storing and verifying the resulting path structure
|
|
content := []byte("test content for path verification")
|
|
key := imagerequest.Source{Host: testHostCDN, Path: testPathCat}.Key()
|
|
|
|
_, err = storage.Store(key, content)
|
|
if err != nil {
|
|
t.Fatalf("Store() error = %v", err)
|
|
}
|
|
|
|
keyStr := string(key)
|
|
|
|
expectedPath := filepath.Join(tmpDir, keyStr[0:2], keyStr[2:4], keyStr)
|
|
|
|
_, err = os.Stat(expectedPath)
|
|
if err != nil {
|
|
t.Errorf("File not at expected path %s: %v", expectedPath, err)
|
|
}
|
|
}
|
|
|
|
// TestVariantStorage_StoreLogsFailedMetaWrite verifies that a .meta write
|
|
// that fails is logged at warn and the store still succeeds.
|
|
func TestVariantStorage_StoreLogsFailedMetaWrite(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var logBuf bytes.Buffer
|
|
|
|
storage, err := NewVariantStorage(
|
|
t.TempDir(), slog.New(slog.NewJSONHandler(&logBuf, nil)))
|
|
if err != nil {
|
|
t.Fatalf("NewVariantStorage() error = %v", err)
|
|
}
|
|
|
|
key := imagerequest.CacheKey(&imagerequest.Request{
|
|
Source: imagerequest.Source{Host: testHostCDN, Path: testPathCat},
|
|
})
|
|
|
|
// A directory where the .meta file goes makes the .meta write fail.
|
|
err = os.MkdirAll(storage.keyToPath(key)+".meta", StorageDirPerm)
|
|
if err != nil {
|
|
t.Fatalf("failed to create directory: %v", err)
|
|
}
|
|
|
|
_, err = storage.Store(key, bytes.NewReader([]byte("variant data")), "image/webp")
|
|
if err != nil {
|
|
t.Fatalf("Store() error = %v, want nil", err)
|
|
}
|
|
|
|
want := `"level":"WARN","msg":"failed to write variant metadata sidecar"`
|
|
if !strings.Contains(logBuf.String(), want) {
|
|
t.Errorf("log missing %s; got %q", want, logBuf.String())
|
|
}
|
|
}
|
|
|
|
// storeTestVariant stores one variant, with its .meta file, in a new
|
|
// VariantStorage and returns the storage and the variant's key.
|
|
func storeTestVariant(t *testing.T) (*VariantStorage, imagerequest.VariantKey) {
|
|
t.Helper()
|
|
|
|
storage, err := NewVariantStorage(t.TempDir(), slog.New(slog.DiscardHandler))
|
|
if err != nil {
|
|
t.Fatalf("NewVariantStorage() error = %v", err)
|
|
}
|
|
|
|
key := imagerequest.CacheKey(&imagerequest.Request{
|
|
Source: imagerequest.Source{Host: testHostCDN, Path: testPathCat},
|
|
})
|
|
|
|
_, err = storage.Store(key, bytes.NewReader([]byte("variant data")), "image/webp")
|
|
if err != nil {
|
|
t.Fatalf("Store() error = %v", err)
|
|
}
|
|
|
|
return storage, key
|
|
}
|
|
|
|
// TestVariantStorage_DeleteRemovesMeta verifies that Delete removes the
|
|
// variant's .meta file along with the variant file.
|
|
func TestVariantStorage_DeleteRemovesMeta(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
storage, key := storeTestVariant(t)
|
|
metaPath := storage.keyToPath(key) + ".meta"
|
|
|
|
_, err := os.Stat(metaPath)
|
|
if err != nil {
|
|
t.Fatalf("Store() wrote no .meta file: %v", err)
|
|
}
|
|
|
|
err = storage.Delete(key)
|
|
if err != nil {
|
|
t.Fatalf("Delete() error = %v", err)
|
|
}
|
|
|
|
if storage.Exists(key) {
|
|
t.Error("Exists() = true after delete, want false")
|
|
}
|
|
|
|
_, err = os.Stat(metaPath)
|
|
if !os.IsNotExist(err) {
|
|
t.Errorf(".meta file left after Delete() (stat err=%v)", err)
|
|
}
|
|
}
|
|
|
|
// TestVariantStorage_DeleteWithoutMeta verifies that Delete succeeds for a
|
|
// variant whose .meta file is missing.
|
|
func TestVariantStorage_DeleteWithoutMeta(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
storage, key := storeTestVariant(t)
|
|
|
|
err := os.Remove(storage.keyToPath(key) + ".meta")
|
|
if err != nil {
|
|
t.Fatalf("removing .meta file: %v", err)
|
|
}
|
|
|
|
err = storage.Delete(key)
|
|
if err != nil {
|
|
t.Fatalf("Delete() error = %v, want nil", err)
|
|
}
|
|
|
|
if storage.Exists(key) {
|
|
t.Error("Exists() = true after delete, want false")
|
|
}
|
|
}
|