using Xunit; using Moq; using FluentAssertions; using Microsoft.Extensions.Logging; using IamService.API.Application.Commands.Groups; using IamService.Domain.AggregatesModel.GroupAggregate; using IamService.Domain.AggregatesModel.OrganizationAggregate; using IamService.Domain.Exceptions; using IamService.Domain.SeedWork; namespace IamService.UnitTests.Application.Commands.Groups; /// /// EN: Unit tests for CreateGroupCommandHandler. /// VI: Unit tests cho CreateGroupCommandHandler. /// public class CreateGroupCommandHandlerTests { private readonly Mock _groupRepositoryMock; private readonly Mock _organizationRepositoryMock; private readonly Mock> _loggerMock; private readonly Mock _unitOfWorkMock; private readonly CreateGroupCommandHandler _handler; public CreateGroupCommandHandlerTests() { _groupRepositoryMock = new Mock(); _organizationRepositoryMock = new Mock(); _loggerMock = new Mock>(); _unitOfWorkMock = new Mock(); // EN: Setup UnitOfWork mock // VI: Thiết lập mock cho UnitOfWork _unitOfWorkMock .Setup(u => u.SaveEntitiesAsync(It.IsAny())) .ReturnsAsync(true); _groupRepositoryMock .Setup(r => r.UnitOfWork) .Returns(_unitOfWorkMock.Object); _handler = new CreateGroupCommandHandler( _groupRepositoryMock.Object, _organizationRepositoryMock.Object, _loggerMock.Object); } [Fact] public async Task Handle_ValidCommand_CreatesGroupAndReturnsResult() { // Arrange var organizationId = Guid.NewGuid(); var command = new CreateGroupCommand( Name: "Development Team", OrganizationId: organizationId, Description: "Main development team"); var organization = Organization.Create("Test Org", "test-org"); _organizationRepositoryMock .Setup(r => r.GetByIdAsync(organizationId, It.IsAny())) .ReturnsAsync(organization); Group? capturedGroup = null; _groupRepositoryMock .Setup(r => r.Add(It.IsAny())) .Callback(g => capturedGroup = g) .Returns((Group g) => g); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.Id.Should().NotBeEmpty(); result.Name.Should().Be("Development Team"); result.Description.Should().Be("Main development team"); result.OrganizationId.Should().Be(organizationId); result.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); capturedGroup.Should().NotBeNull(); capturedGroup!.OrganizationId.Should().Be(organizationId); } [Fact] public async Task Handle_ValidCommand_PersistsGroupToRepository() { // Arrange var organizationId = Guid.NewGuid(); var command = new CreateGroupCommand( Name: "Dev Team", OrganizationId: organizationId); var organization = Organization.Create("Test Org", "test-org"); _organizationRepositoryMock .Setup(r => r.GetByIdAsync(organizationId, It.IsAny())) .ReturnsAsync(organization); _groupRepositoryMock .Setup(r => r.Add(It.IsAny())) .Returns((Group g) => g); // Act await _handler.Handle(command, CancellationToken.None); // Assert _groupRepositoryMock.Verify(r => r.Add(It.IsAny()), Times.Once); _unitOfWorkMock.Verify(u => u.SaveEntitiesAsync(It.IsAny()), Times.Once); } [Fact] public async Task Handle_OrganizationNotFound_ThrowsDomainException() { // Arrange var command = new CreateGroupCommand( Name: "Dev Team", OrganizationId: Guid.NewGuid()); _organizationRepositoryMock .Setup(r => r.GetByIdAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((Organization?)null); // Act var act = async () => await _handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync() .WithMessage("*Organization*not found*"); _groupRepositoryMock.Verify(r => r.Add(It.IsAny()), Times.Never); } [Fact] public async Task Handle_WithoutDescription_CreatesGroupWithNullDescription() { // Arrange var organizationId = Guid.NewGuid(); var command = new CreateGroupCommand( Name: "Dev Team", OrganizationId: organizationId, Description: null); var organization = Organization.Create("Test Org", "test-org"); _organizationRepositoryMock .Setup(r => r.GetByIdAsync(organizationId, It.IsAny())) .ReturnsAsync(organization); _groupRepositoryMock .Setup(r => r.Add(It.IsAny())) .Returns((Group g) => g); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Description.Should().BeNull(); } [Fact] public async Task Handle_CancellationRequested_PropagatesCancellation() { // Arrange var organizationId = Guid.NewGuid(); var command = new CreateGroupCommand("Dev Team", organizationId); var cts = new CancellationTokenSource(); cts.Cancel(); _organizationRepositoryMock .Setup(r => r.GetByIdAsync(It.IsAny(), It.IsAny())) .ThrowsAsync(new OperationCanceledException()); // Act var act = async () => await _handler.Handle(command, cts.Token); // Assert await act.Should().ThrowAsync(); } }