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 <noreply@anthropic.com>
This commit is contained in:
Paul Schneider 2026-09-25 21:33:13 +01:00
commit 7844449c99
Signed by: notazof
GPG key ID: AD4B9B545FBD7D12
3 changed files with 53 additions and 2 deletions

View file

@ -82,6 +82,15 @@ et négociation HTTP/3 côté client PostIt.
désactivé (grisé) et inerte au clic. Corrigé en désactivé (grisé) et inerte au clic. Corrigé en
`$parent[ContentPage].((vm:XxxViewModel)DataContext).Cmd` (syntaxe `$parent[ContentPage].((vm:XxxViewModel)DataContext).Cmd` (syntaxe
déjà en vigueur dans `CirclesPage` / `EstimateListPage`). 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 ## [1.0.8-rc17] - unstable

View file

@ -363,4 +363,35 @@ public sealed class FileSystemApiTests : IClassFixture<BlogsWebServerFixture>
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
/// <summary>
/// Regression for the "delete returns 40x yet the file is gone"
/// report: <c>DeleteUserDirOrFile</c> read <c>FileInfo.Length</c>
/// <b>after</b> <c>fi.Delete()</c>, and <c>FileInfo.Length</c>
/// throws <c>FileNotFoundException</c> once the file no longer
/// exists. The controller's <c>catch</c> 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.
/// </summary>
[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.");
}
} }

View file

@ -70,8 +70,12 @@ namespace Yavsc.Server.Helpers
var root = Path.Combine(siteSettings.Blog, user.UserName); var root = Path.Combine(siteSettings.Blog, user.UserName);
var fi = new FileInfo(Path.Combine(root, fileName)); var fi = new FileInfo(Path.Combine(root, fileName));
if (!fi.Exists) return; 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(); fi.Delete();
user.DiskUsage -= fi.Length; user.DiskUsage -= length;
} }
public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName, SiteSettings siteSettings) 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)); var fi = new FileInfo(Path.Combine(root, dirName));
if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "non existent" }; 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(); fi.Delete();
user.DiskUsage -= fi.Length; user.DiskUsage -= length;
} }
else else
{ {