feat(template-folder): add TemplateFolderService.CreateFolderAsync with validation
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user