From 3213b605be4f8f9938b093a3384d404d66e7a28b Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Sat, 25 Jul 2026 08:11:10 +0200 Subject: [PATCH] test(http): cover scope-safe removal on TUS upload eviction Refs GHSA-m9f5-2232-frp6 --- http/upload_cache_memory_test.go | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 http/upload_cache_memory_test.go diff --git a/http/upload_cache_memory_test.go b/http/upload_cache_memory_test.go new file mode 100644 index 00000000..399d6078 --- /dev/null +++ b/http/upload_cache_memory_test.go @@ -0,0 +1,42 @@ +package fbhttp + +import ( + "os" + "path/filepath" + "sync" + "testing" + "time" +) + +// On expiry the memory upload cache must delete an abandoned upload through the +// registered scoped removal callback, not with a raw os.Remove on the cached +// path. This test registers an entry keyed by a real file's path whose callback +// does not delete the file; if the old raw os.Remove behaviour were still in +// place the file (whose path is the key) would be removed. +func TestMemoryUploadCacheEvictionUsesRemovalCallback(t *testing.T) { + c := newMemoryUploadCache() + t.Cleanup(c.Close) + + f := filepath.Join(t.TempDir(), "partial.upload") + if err := os.WriteFile(f, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + done := make(chan struct{}) + var once sync.Once + // Deliberately do NOT delete the file here. + c.cache.Set(f, memoryUploadEntry{size: 1, remove: func() error { + once.Do(func() { close(done) }) + return nil + }}, time.Millisecond) + + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("eviction did not invoke the removal callback") + } + + if _, err := os.Stat(f); err != nil { + t.Fatalf("file at the cache key was deleted, so eviction bypassed the callback (raw os.Remove?): %v", err) + } +}