From 3dfc7180c53b6a23711e7103a9f778c803c6370a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 11 May 2026 10:53:43 -0400 Subject: [PATCH] feat(template-folder): rename folder with sibling uniqueness check --- .../Services/TemplateFolderService.cs | 25 ++++++++++++ .../Services/TemplateFolderServiceTests.cs | 40 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/ScadaLink.TemplateEngine/Services/TemplateFolderService.cs b/src/ScadaLink.TemplateEngine/Services/TemplateFolderService.cs index 53ee7c9..eeceef6 100644 --- a/src/ScadaLink.TemplateEngine/Services/TemplateFolderService.cs +++ b/src/ScadaLink.TemplateEngine/Services/TemplateFolderService.cs @@ -42,4 +42,29 @@ public class TemplateFolderService return Result.Success(folder); } + + public async Task> RenameFolderAsync( + int folderId, string newName, string user, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(newName)) + return Result.Failure("Folder name is required."); + + var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken); + if (folder == null) + return Result.Failure($"Folder with ID {folderId} not found."); + + var all = await _repository.GetAllFoldersAsync(cancellationToken); + if (all.Any(f => f.Id != folderId + && f.ParentFolderId == folder.ParentFolderId + && string.Equals(f.Name, newName, StringComparison.OrdinalIgnoreCase))) + return Result.Failure($"A folder named '{newName}' already exists at this level."); + + folder.Name = newName; + await _repository.UpdateFolderAsync(folder, cancellationToken); + await _repository.SaveChangesAsync(cancellationToken); + await _auditService.LogAsync(user, "Update", "TemplateFolder", folder.Id.ToString(), newName, folder, cancellationToken); + + return Result.Success(folder); + } } diff --git a/tests/ScadaLink.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs b/tests/ScadaLink.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs index a8c7327..f4efe25 100644 --- a/tests/ScadaLink.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs +++ b/tests/ScadaLink.TemplateEngine.Tests/Services/TemplateFolderServiceTests.cs @@ -67,4 +67,44 @@ public class TemplateFolderServiceTests Assert.True(result.IsFailure); Assert.Contains("not found", result.Error); } + + [Fact] + public async Task RenameFolder_ValidInput_ReturnsSuccess() + { + var folder = new TemplateFolder("Old") { Id = 1, ParentFolderId = null }; + _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny())).ReturnsAsync(folder); + _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny())) + .ReturnsAsync(new List { folder }); + + var result = await _sut.RenameFolderAsync(1, "New", "admin"); + + Assert.True(result.IsSuccess); + Assert.Equal("New", result.Value.Name); + } + + [Fact] + public async Task RenameFolder_NotFound_ReturnsFailure() + { + _repoMock.Setup(r => r.GetFolderByIdAsync(99, It.IsAny())) + .ReturnsAsync((TemplateFolder?)null); + + var result = await _sut.RenameFolderAsync(99, "New", "admin"); + + Assert.True(result.IsFailure); + } + + [Fact] + public async Task RenameFolder_DuplicateSibling_ReturnsFailure() + { + var folder = new TemplateFolder("Old") { Id = 1, ParentFolderId = null }; + var sibling = new TemplateFolder("Other") { Id = 2, ParentFolderId = null }; + _repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny())).ReturnsAsync(folder); + _repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny())) + .ReturnsAsync(new List { folder, sibling }); + + var result = await _sut.RenameFolderAsync(1, "Other", "admin"); + + Assert.True(result.IsFailure); + Assert.Contains("already exists", result.Error); + } }