fix(m9/T23a): assign distinct SortOrder on folder create (so reorder is visible)

This commit is contained in:
Joseph Doherty
2026-06-18 11:15:18 -04:00
parent 314c7dea23
commit 9a73094f03
2 changed files with 96 additions and 1 deletions
@@ -378,6 +378,98 @@ public class TemplateFolderServiceTests
Assert.Contains("not found", result.Error);
}
// ========================================================================
// CreateFolderAsync — distinct SortOrder assignment (max+1 among siblings)
// ========================================================================
[Fact]
public async Task CreateFolder_FirstSibling_GetsSortOrderZero()
{
// No existing siblings at root → first folder gets SortOrder 0.
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder>());
TemplateFolder? captured = null;
_repoMock.Setup(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()))
.Callback<TemplateFolder, CancellationToken>((f, _) => captured = f);
var result = await _sut.CreateFolderAsync("Alpha", null, "admin");
Assert.True(result.IsSuccess);
Assert.NotNull(captured);
Assert.Equal(0, captured!.SortOrder);
}
[Fact]
public async Task CreateFolder_SecondSiblingUnderSameParent_GetsSortOrderOne()
{
// One existing root sibling with SortOrder=0 → new folder gets SortOrder 1.
var existing = new TemplateFolder("Alpha") { Id = 1, ParentFolderId = null, SortOrder = 0 };
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder> { existing });
TemplateFolder? captured = null;
_repoMock.Setup(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()))
.Callback<TemplateFolder, CancellationToken>((f, _) => captured = f);
var result = await _sut.CreateFolderAsync("Beta", null, "admin");
Assert.True(result.IsSuccess);
Assert.NotNull(captured);
Assert.Equal(1, captured!.SortOrder);
}
[Fact]
public async Task CreateFolder_SortOrdersDoNotInterferAcrossParents()
{
// A root folder (SortOrder=5) should NOT influence a subfolder's SortOrder
// when the subfolder has no siblings of its own.
var root = new TemplateFolder("Root") { Id = 1, ParentFolderId = null, SortOrder = 5 };
_repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(root);
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder> { root });
TemplateFolder? captured = null;
_repoMock.Setup(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()))
.Callback<TemplateFolder, CancellationToken>((f, _) => captured = f);
// Create a child under root — it has no siblings, so SortOrder should be 0.
var result = await _sut.CreateFolderAsync("Child", 1, "admin");
Assert.True(result.IsSuccess);
Assert.NotNull(captured);
Assert.Equal(0, captured!.SortOrder);
}
[Fact]
public async Task CreateFolder_TwoSiblingsReorderIsVisiblyEffective()
{
// Regression: when two newly-created siblings have distinct SortOrders (0, 1),
// a Move-up on the second-created folder must visibly change the order.
//
// Simulate creating two folders sequentially. The first gets SortOrder=0,
// the second gets SortOrder=1 (as guaranteed by the max+1 logic under fix).
// Then reorder the second folder Up — it should swap to SortOrder=0,
// moving it before the first.
var first = new TemplateFolder("First") { Id = 1, ParentFolderId = null, SortOrder = 0 };
var second = new TemplateFolder("Second") { Id = 2, ParentFolderId = null, SortOrder = 1 };
var all = new List<TemplateFolder> { first, second };
_repoMock.Setup(r => r.GetFolderByIdAsync(2, It.IsAny<CancellationToken>())).ReturnsAsync(second);
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>())).ReturnsAsync(all);
var result = await _sut.ReorderFolderAsync(2, ReorderDirection.Up, "admin");
Assert.True(result.IsSuccess);
// After a Move-up the second folder should be at SortOrder 0 (before the first).
Assert.Equal(0, second.SortOrder);
Assert.Equal(1, first.SortOrder);
// Verify the swap was actually persisted.
_repoMock.Verify(r => r.UpdateFolderAsync(first, It.IsAny<CancellationToken>()), Times.Once);
_repoMock.Verify(r => r.UpdateFolderAsync(second, It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task ReorderFolder_Persists_And_WritesAudit()
{