feat(template-folder): delete folder blocked if non-empty

This commit is contained in:
Joseph Doherty
2026-05-11 10:59:29 -04:00
parent e44bbc0caf
commit 723ab61bd8
2 changed files with 83 additions and 0 deletions

View File

@@ -122,4 +122,37 @@ public class TemplateFolderService
return Result<TemplateFolder>.Success(folder);
}
public async Task<Result<bool>> DeleteFolderAsync(
int folderId, string user,
CancellationToken cancellationToken = default)
{
var folder = await _repository.GetFolderByIdAsync(folderId, cancellationToken);
if (folder == null)
return Result<bool>.Failure($"Folder with ID {folderId} not found.");
var allFolders = await _repository.GetAllFoldersAsync(cancellationToken);
var allTemplates = await _repository.GetAllTemplatesAsync(cancellationToken);
var childFolderCount = allFolders.Count(f => f.ParentFolderId == folderId);
var childTemplateCount = allTemplates.Count(t => t.FolderId == folderId);
if (childFolderCount > 0 || childTemplateCount > 0)
{
var parts = new List<string>();
if (childTemplateCount > 0)
parts.Add($"{childTemplateCount} template{(childTemplateCount == 1 ? "" : "s")}");
if (childFolderCount > 0)
parts.Add($"{childFolderCount} subfolder{(childFolderCount == 1 ? "" : "s")}");
return Result<bool>.Failure(
$"Cannot delete folder '{folder.Name}': it contains {string.Join(" and ", parts)}. " +
"Move or delete contents first.");
}
await _repository.DeleteFolderAsync(folderId, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
await _auditService.LogAsync(user, "Delete", "TemplateFolder", folderId.ToString(), folder.Name, null, cancellationToken);
return Result<bool>.Success(true);
}
}

View File

@@ -186,4 +186,54 @@ public class TemplateFolderServiceTests
Assert.True(result.IsFailure);
Assert.Contains("cycle", result.Error, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task DeleteFolder_Empty_ReturnsSuccess()
{
var f = new TemplateFolder("Empty") { Id = 1 };
_repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f);
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder> { f });
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Template>());
var result = await _sut.DeleteFolderAsync(1, "admin");
Assert.True(result.IsSuccess);
_repoMock.Verify(r => r.DeleteFolderAsync(1, It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task DeleteFolder_HasChildFolders_ReturnsFailure_WithCounts()
{
var parent = new TemplateFolder("P") { Id = 1 };
var child = new TemplateFolder("C") { Id = 2, ParentFolderId = 1 };
_repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(parent);
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder> { parent, child });
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Template>());
var result = await _sut.DeleteFolderAsync(1, "admin");
Assert.True(result.IsFailure);
Assert.Contains("1 subfolder", result.Error);
}
[Fact]
public async Task DeleteFolder_HasTemplates_ReturnsFailure_WithCounts()
{
var f = new TemplateFolder("P") { Id = 1 };
var t = new Template("X") { Id = 5, FolderId = 1 };
_repoMock.Setup(r => r.GetFolderByIdAsync(1, It.IsAny<CancellationToken>())).ReturnsAsync(f);
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder> { f });
_repoMock.Setup(r => r.GetAllTemplatesAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Template> { t });
var result = await _sut.DeleteFolderAsync(1, "admin");
Assert.True(result.IsFailure);
Assert.Contains("1 template", result.Error);
}
}