feat(template-folder): add TemplateFolderService.CreateFolderAsync with validation

This commit is contained in:
Joseph Doherty
2026-05-11 10:50:28 -04:00
parent 44c6e4a553
commit ff23f64cf8
2 changed files with 115 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.Commons.Types;
namespace ScadaLink.TemplateEngine.Services;
public class TemplateFolderService
{
private readonly ITemplateEngineRepository _repository;
private readonly IAuditService _auditService;
public TemplateFolderService(ITemplateEngineRepository repository, IAuditService auditService)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
_auditService = auditService ?? throw new ArgumentNullException(nameof(auditService));
}
public async Task<Result<TemplateFolder>> CreateFolderAsync(
string name, int? parentFolderId, string user,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(name))
return Result<TemplateFolder>.Failure("Folder name is required.");
if (parentFolderId.HasValue)
{
var parent = await _repository.GetFolderByIdAsync(parentFolderId.Value, cancellationToken);
if (parent == null)
return Result<TemplateFolder>.Failure($"Parent folder with ID {parentFolderId.Value} not found.");
}
var all = await _repository.GetAllFoldersAsync(cancellationToken);
if (all.Any(f => f.ParentFolderId == parentFolderId
&& string.Equals(f.Name, name, StringComparison.OrdinalIgnoreCase)))
return Result<TemplateFolder>.Failure($"A folder named '{name}' already exists at this level.");
var folder = new TemplateFolder(name) { ParentFolderId = parentFolderId };
await _repository.AddFolderAsync(folder, cancellationToken);
await _repository.SaveChangesAsync(cancellationToken);
await _auditService.LogAsync(user, "Create", "TemplateFolder", folder.Id.ToString(), name, folder, cancellationToken);
return Result<TemplateFolder>.Success(folder);
}
}

View File

@@ -0,0 +1,70 @@
using Moq;
using ScadaLink.Commons.Entities.Templates;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Interfaces.Services;
using ScadaLink.TemplateEngine.Services;
namespace ScadaLink.TemplateEngine.Tests.Services;
public class TemplateFolderServiceTests
{
private readonly Mock<ITemplateEngineRepository> _repoMock = new();
private readonly Mock<IAuditService> _auditMock = new();
private readonly TemplateFolderService _sut;
public TemplateFolderServiceTests()
{
_sut = new TemplateFolderService(_repoMock.Object, _auditMock.Object);
}
[Fact]
public async Task CreateFolder_ValidInput_ReturnsSuccess()
{
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder>());
var result = await _sut.CreateFolderAsync("Dev", null, "admin");
Assert.True(result.IsSuccess);
Assert.Equal("Dev", result.Value.Name);
Assert.Null(result.Value.ParentFolderId);
_repoMock.Verify(r => r.AddFolderAsync(It.IsAny<TemplateFolder>(), It.IsAny<CancellationToken>()), Times.Once);
_repoMock.Verify(r => r.SaveChangesAsync(It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task CreateFolder_EmptyName_ReturnsFailure()
{
var result = await _sut.CreateFolderAsync(" ", null, "admin");
Assert.True(result.IsFailure);
Assert.Contains("required", result.Error, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task CreateFolder_DuplicateSiblingName_CaseInsensitive_ReturnsFailure()
{
_repoMock.Setup(r => r.GetAllFoldersAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<TemplateFolder>
{
new("Dev") { Id = 1, ParentFolderId = null }
});
var result = await _sut.CreateFolderAsync("dev", null, "admin");
Assert.True(result.IsFailure);
Assert.Contains("already exists", result.Error);
}
[Fact]
public async Task CreateFolder_ParentNotFound_ReturnsFailure()
{
_repoMock.Setup(r => r.GetFolderByIdAsync(99, It.IsAny<CancellationToken>()))
.ReturnsAsync((TemplateFolder?)null);
var result = await _sut.CreateFolderAsync("Sub", 99, "admin");
Assert.True(result.IsFailure);
Assert.Contains("not found", result.Error);
}
}