From acec4c31170f7b8d2f1f00ec2f01b279e0038379 Mon Sep 17 00:00:00 2001 From: Sascha Buehrle <47737812+saschabuehrle@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:49:53 +0200 Subject: [PATCH] fix: invalidate storage size cache on delete and move (#726) --- internal/http/storage.go | 6 +++++ internal/http/storage_test.go | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/internal/http/storage.go b/internal/http/storage.go index 63a821d2..0014e666 100644 --- a/internal/http/storage.go +++ b/internal/http/storage.go @@ -429,6 +429,9 @@ func (h *StorageHandler) handleDelete(w http.ResponseWriter, r *http.Request) { return } + // Invalidate cached size for this tenant after successful deletion. + h.sizeCache.Delete(delBase) + slog.Info("storage.deleted", "path", relPath) writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"}) } @@ -631,6 +634,9 @@ func (h *StorageHandler) handleMove(w http.ResponseWriter, r *http.Request) { return } + // Invalidate cached size for this tenant after successful move. + h.sizeCache.Delete(base) + slog.Info("storage.moved", "from", fromRel, "to", toRel) writeJSON(w, http.StatusOK, map[string]any{ "from": fromRel, diff --git a/internal/http/storage_test.go b/internal/http/storage_test.go index b76fff07..905ff709 100644 --- a/internal/http/storage_test.go +++ b/internal/http/storage_test.go @@ -3,6 +3,7 @@ package http import ( "context" "encoding/json" + "net/http" "net/http/httptest" "os" "path/filepath" @@ -177,3 +178,46 @@ func TestIsHiddenPathOnlyAffectsMaster(t *testing.T) { t.Fatal("partial match should not be hidden") } } + +func TestStorageDeleteInvalidatesSizeCache(t *testing.T) { + baseDir := t.TempDir() + writeStorageTestFile(t, filepath.Join(baseDir, "tmp.txt"), "abc") + + handler := NewStorageHandler(baseDir) + req := httptest.NewRequest(http.MethodDelete, "/v1/storage/files/tmp.txt", nil) + req = req.WithContext(store.WithTenantID(context.Background(), store.MasterTenantID)) + req.SetPathValue("path", "tmp.txt") + + sizeBase := handler.tenantBaseDir(req) + handler.sizeCache.Store(sizeBase, &sizeCacheEntry{total: 3, files: 1}) + + w := httptest.NewRecorder() + handler.handleDelete(w, req) + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200", w.Code) + } + if _, ok := handler.sizeCache.Load(sizeBase); ok { + t.Fatal("expected size cache entry to be invalidated after delete") + } +} + +func TestStorageMoveInvalidatesSizeCache(t *testing.T) { + baseDir := t.TempDir() + writeStorageTestFile(t, filepath.Join(baseDir, "from.txt"), "abc") + + handler := NewStorageHandler(baseDir) + req := httptest.NewRequest(http.MethodPut, "/v1/storage/move?from=from.txt&to=to.txt", nil) + req = req.WithContext(store.WithTenantID(context.Background(), store.MasterTenantID)) + + sizeBase := handler.tenantBaseDir(req) + handler.sizeCache.Store(sizeBase, &sizeCacheEntry{total: 3, files: 1}) + + w := httptest.NewRecorder() + handler.handleMove(w, req) + if w.Code != http.StatusOK { + t.Fatalf("status = %d, want 200", w.Code) + } + if _, ok := handler.sizeCache.Load(sizeBase); ok { + t.Fatal("expected size cache entry to be invalidated after move") + } +}