From 7844449c9964d4f3853c7b83e962020cdd64ae58 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Fri, 25 Sep 2026 21:33:13 +0100 Subject: [PATCH] Blogs: fix delete returning 40x despite the file being removed DeleteUserDirOrFile (and DeleteUserFile) read FileInfo.Length AFTER fi.Delete(), and FileInfo.Length throws FileNotFoundException once the file is gone. The controller's catch turned that into a 400, so the user saw "le fichier n'existe pas sur le serveur" while the delete had in fact succeeded. Capture the length before deleting. Adds FileSystemApiTests.Delete_fs_removes_the_file_and_returns_200 (asserts 200 + file gone from disk). 8/8 FileSystemApiTests pass. Co-Authored-By: Claude Code --- CHANGELOG.md | 9 ++++++ src/Yavsc.Blogs.Tests/FileSystemApiTests.cs | 31 +++++++++++++++++++ src/Yavsc.Server/Helpers/FileSystemHelpers.cs | 15 +++++++-- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e87dee655..d15974d0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,15 @@ et négociation HTTP/3 côté client PostIt. désactivé (grisé) et inerte au clic. Corrigé en `$parent[ContentPage].((vm:XxxViewModel)DataContext).Cmd` (syntaxe déjà en vigueur dans `CirclesPage` / `EstimateListPage`). +* [Yavsc.Blogs] La suppression d'un fichier (`DELETE api/v1/fs/{name}`) + renvoyait un 40x alors que le fichier était bien supprimé du disque. + `DeleteUserDirOrFile` (et `DeleteUserFile`) lisaient + `FileInfo.Length` **après** `fi.Delete()` ; or `FileInfo.Length` + lève `FileNotFoundException` une fois le fichier disparu — le `catch` + du contrôleur transformait cette exception en 400, d'où le message + perçu « le fichier n'existe pas sur le serveur » tandis que la + suppression avait réussi. La taille est désormais capturée avant la + suppression. ## [1.0.8-rc17] - unstable diff --git a/src/Yavsc.Blogs.Tests/FileSystemApiTests.cs b/src/Yavsc.Blogs.Tests/FileSystemApiTests.cs index e67f235aa..0e36a6018 100644 --- a/src/Yavsc.Blogs.Tests/FileSystemApiTests.cs +++ b/src/Yavsc.Blogs.Tests/FileSystemApiTests.cs @@ -363,4 +363,35 @@ public sealed class FileSystemApiTests : IClassFixture Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + + /// + /// Regression for the "delete returns 40x yet the file is gone" + /// report: DeleteUserDirOrFile read FileInfo.Length + /// after fi.Delete(), and FileInfo.Length + /// throws FileNotFoundException once the file no longer + /// exists. The controller's catch turned that into a 400 + /// even though the file had already been removed — the user saw + /// "le fichier n'existe pas sur le serveur" while the delete in + /// fact succeeded. This test asserts a clean 200 and that the + /// file is gone from disk. + /// + [Fact] + public async Task Delete_fs_removes_the_file_and_returns_200() + { + _fixture.ResetDatabase(); + _fixture.SeedUser("alice"); + + var bytes = System.Text.Encoding.UTF8.GetBytes("delete me"); + WritePhysicalFile(_fixture, "alice", "doomed.txt", bytes); + + using var http = NewClient("alice"); + var response = await http.DeleteAsync( + "/api/v1/fs/doomed.txt", TestContext.Current.CancellationToken); + var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken); + + Assert.True(response.StatusCode == HttpStatusCode.OK, + $"Expected 200, got {(int)response.StatusCode} ({response.StatusCode}): {body}"); + Assert.False(File.Exists(PhysicalFile(_fixture, "alice", "doomed.txt")), + "The file must be gone from disk after a successful delete."); + } } \ No newline at end of file diff --git a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs index 762c18f36..e0d5b8b9d 100644 --- a/src/Yavsc.Server/Helpers/FileSystemHelpers.cs +++ b/src/Yavsc.Server/Helpers/FileSystemHelpers.cs @@ -70,8 +70,12 @@ namespace Yavsc.Server.Helpers var root = Path.Combine(siteSettings.Blog, user.UserName); var fi = new FileInfo(Path.Combine(root, fileName)); if (!fi.Exists) return; + // Capture the length BEFORE deleting: FileInfo.Length throws + // FileNotFoundException once the file is gone, which would + // surface as a 400 even though the delete already succeeded. + var length = fi.Length; fi.Delete(); - user.DiskUsage -= fi.Length; + user.DiskUsage -= length; } public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName, SiteSettings siteSettings) @@ -85,8 +89,15 @@ namespace Yavsc.Server.Helpers { var fi = new FileInfo(Path.Combine(root, dirName)); if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "non existent" }; + // Capture the length BEFORE deleting: FileInfo.Length + // throws FileNotFoundException once the file is gone, so + // reading it after fi.Delete() would throw — the caller's + // catch turns that into a 400 even though the file was + // already removed (the user sees "file does not exist" + // while the delete in fact succeeded). + var length = fi.Length; fi.Delete(); - user.DiskUsage -= fi.Length; + user.DiskUsage -= length; } else {