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
This commit is contained in:
2026-09-29 09:28:38 +00:00
parent c7e4eacd87
commit 8be21c005f
@@ -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 // TestSecondHitDoesNotReadMetaFile checks that once a variant has been
// stored or read, a hit takes its content type from memory: with the // stored or read, a hit takes its content type from memory: with the
// .meta file deleted, GetVariant must still return the stored content // .meta file deleted, GetVariant must still return the stored content
@@ -145,6 +155,43 @@ func TestReadDuringStoreKeepsStoredContentType(t *testing.T) {
assertVariantServed(t, cache, testVariantKeyOne, content) 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 // TestEvictedVariantIsNotServed checks that a variant the evictor
// removed is a miss and cannot be read, although it had been stored // removed is a miss and cannot be read, although it had been stored
// and served before. // and served before.