From 8be21c005f2a1ef86eecf560bc49f735af5cafdd Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 29 Sep 2026 08:26:48 +0000 Subject: [PATCH] Test that a failed read during a store keeps the stored content type (closes #70) A read that finds a variant in memory but cannot open its file removes it from memory. When that happens after StoreVariant has added the variant's content type, a read that opened the variant file before the store wrote its .meta file finds memory empty and keeps application/octet-stream there, so every later hit serves the image with it. The test runs the store, the failed read and the read without a .meta file in that order, then checks a later hit. It fails now. Model: opus-5-5 --- internal/imgcache/metacache_internal_test.go | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/internal/imgcache/metacache_internal_test.go b/internal/imgcache/metacache_internal_test.go index c327249..14be0f4 100644 --- a/internal/imgcache/metacache_internal_test.go +++ b/internal/imgcache/metacache_internal_test.go @@ -83,6 +83,16 @@ func assertLookupMisses(t *testing.T, cache *Cache, request *ImageRequest) { } } +// renameFile renames the file at from to to. +func renameFile(t *testing.T, from, to string) { + t.Helper() + + err := os.Rename(from, to) + if err != nil { + t.Fatalf("renaming %s: %v", from, err) + } +} + // TestSecondHitDoesNotReadMetaFile checks that once a variant has been // stored or read, a hit takes its content type from memory: with the // .meta file deleted, GetVariant must still return the stored content @@ -145,6 +155,43 @@ func TestReadDuringStoreKeepsStoredContentType(t *testing.T) { assertVariantServed(t, cache, testVariantKeyOne, content) } +// TestFailedReadDuringStoreKeepsStoredContentType checks that a read +// which found no .meta file cannot leave application/octet-stream in +// memory, even when another read has removed the content type +// StoreVariant kept there. In this order: the store; a read that finds +// the variant in memory but cannot open its file, and so removes it +// from memory; a read that opened the variant file before the store +// wrote its .meta file. Later hits must get the stored content type. +func TestFailedReadDuringStoreKeepsStoredContentType(t *testing.T) { + t.Parallel() + + cache, _ := newEvictionTestCache(t, 1<<20) + content := []byte("webp variant bytes") + variantPath := cache.variants.keyToPath(testVariantKeyOne) + metaPath := variantPath + ".meta" + + storeEvictionTestVariant(t, cache, testVariantKeyOne, content) + + renameFile(t, variantPath, variantPath+".hidden") + assertVariantNotFound(t, cache, testVariantKeyOne) + renameFile(t, variantPath+".hidden", variantPath) + + renameFile(t, metaPath, metaPath+".hidden") + + reader, _, contentType, err := cache.GetVariant(testVariantKeyOne) + if err != nil { + t.Fatalf("GetVariant(%s) error = %v", testVariantKeyOne, err) + } + + _ = reader.Close() + + t.Logf("the read without a .meta file got content type %q", contentType) + + renameFile(t, metaPath+".hidden", metaPath) + + assertVariantServed(t, cache, testVariantKeyOne, content) +} + // TestEvictedVariantIsNotServed checks that a variant the evictor // removed is a miss and cannot be read, although it had been stored // and served before.