diff --git a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/TemplateFolderService.cs b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/TemplateFolderService.cs index 10a1d443..e989e178 100644 --- a/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/TemplateFolderService.cs +++ b/src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/TemplateFolderService.cs @@ -49,7 +49,10 @@ public class TemplateFolderService && string.Equals(f.Name, name, StringComparison.OrdinalIgnoreCase))) return Result.Failure($"A folder named '{name}' already exists at this level."); - var folder = new TemplateFolder(name) { ParentFolderId = parentFolderId }; + var siblings = all.Where(f => f.ParentFolderId == parentFolderId).ToList(); + var sortOrder = siblings.Count == 0 ? 0 : siblings.Max(f => f.SortOrder) + 1; + + var folder = new TemplateFolder(name) { ParentFolderId = parentFolderId, SortOrder = sortOrder }; await _repository.AddFolderAsync(folder, cancellationToken); await _repository.SaveChangesAsync(cancellationToken); await _auditService.LogAsync(user, "Create", "TemplateFolder", folder.Id.ToString(), name, folder, cancellationToken); diff --git a/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs b/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs index af2a5e51..9c729b0e 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs @@ -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())) + .ReturnsAsync(new List()); + + TemplateFolder? captured = null; + _repoMock.Setup(r => r.AddFolderAsync(It.IsAny(), It.IsAny())) + .Callback((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())) + .ReturnsAsync(new List { existing }); + + TemplateFolder? captured = null; + _repoMock.Setup(r => r.AddFolderAsync(It.IsAny(), It.IsAny())) + .Callback((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())).ReturnsAsync(root); + _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny())) + .ReturnsAsync(new List { root }); + + TemplateFolder? captured = null; + _repoMock.Setup(r => r.AddFolderAsync(It.IsAny(), It.IsAny())) + .Callback((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 { first, second }; + + _repoMock.Setup(r => r.GetFolderByIdAsync(2, It.IsAny())).ReturnsAsync(second); + _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny())).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()), Times.Once); + _repoMock.Verify(r => r.UpdateFolderAsync(second, It.IsAny()), Times.Once); + } + [Fact] public async Task ReorderFolder_Persists_And_WritesAudit() {