using Xunit; using Moq; using FluentAssertions; using Microsoft.Extensions.Logging; using IamService.API.Application.Commands.Groups; using IamService.Domain.AggregatesModel.GroupAggregate; using IamService.Domain.Exceptions; using IamService.Domain.SeedWork; namespace IamService.UnitTests.Application.Commands.Groups; /// /// EN: Unit tests for AddGroupMemberCommandHandler. /// VI: Unit tests cho AddGroupMemberCommandHandler. /// public class AddGroupMemberCommandHandlerTests { private readonly Mock _groupRepositoryMock; private readonly Mock> _loggerMock; private readonly Mock _unitOfWorkMock; private readonly AddGroupMemberCommandHandler _handler; public AddGroupMemberCommandHandlerTests() { _groupRepositoryMock = 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 AddGroupMemberCommandHandler( _groupRepositoryMock.Object, _loggerMock.Object); } [Fact] public async Task Handle_ValidCommand_AddsMemberToGroupAndReturnsResult() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); // EN: Override group's Id using reflection for testing (since Id is auto-generated) // VI: Sử dụng để test vì Id được tự động tạo var idProperty = typeof(Group).GetProperty("Id"); idProperty?.SetValue(group, groupId); var command = new AddGroupMemberCommand(groupId, userId); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); result.GroupId.Should().Be(groupId); result.UserId.Should().Be(userId); result.Role.Should().Be("Member"); // Default role result.JoinedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public async Task Handle_WithSpecificRole_AddsMemberWithCorrectRole() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); var command = new AddGroupMemberCommand( GroupId: groupId, UserId: userId, RoleId: GroupRole.Admin.Id); // Admin role _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Role.Should().Be("Admin"); } [Fact] public async Task Handle_WithAddedByUser_TracksByWhoAdded() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var addedByUserId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); var command = new AddGroupMemberCommand( GroupId: groupId, UserId: userId, AddedByUserId: addedByUserId); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().NotBeNull(); group.Members.First().AddedByUserId.Should().Be(addedByUserId); } [Fact] public async Task Handle_ValidCommand_PersistsChangesToRepository() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); var command = new AddGroupMemberCommand(groupId, userId); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act await _handler.Handle(command, CancellationToken.None); // Assert _groupRepositoryMock.Verify(r => r.Update(group), Times.Once); _unitOfWorkMock.Verify(u => u.SaveEntitiesAsync(It.IsAny()), Times.Once); } [Fact] public async Task Handle_GroupNotFound_ThrowsDomainException() { // Arrange var command = new AddGroupMemberCommand(Guid.NewGuid(), Guid.NewGuid()); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(It.IsAny(), It.IsAny())) .ReturnsAsync((Group?)null); // Act var act = async () => await _handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync() .WithMessage("*Group*not found*"); _groupRepositoryMock.Verify(r => r.Update(It.IsAny()), Times.Never); } [Fact] public async Task Handle_UserAlreadyMember_ThrowsInvalidOperationException() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); group.AddMember(userId); // User already added var command = new AddGroupMemberCommand(groupId, userId); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var act = async () => await _handler.Handle(command, CancellationToken.None); // Assert await act.Should().ThrowAsync() .WithMessage("*already a member*"); } [Fact] public async Task Handle_InvalidRoleId_UsesDefaultRole() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); var command = new AddGroupMemberCommand( GroupId: groupId, UserId: userId, RoleId: 999); // Invalid role ID _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Role.Should().Be("Member"); // Should fall back to Member } [Fact] public async Task Handle_CancellationRequested_PropagatesCancellation() { // Arrange var command = new AddGroupMemberCommand(Guid.NewGuid(), Guid.NewGuid()); var cts = new CancellationTokenSource(); cts.Cancel(); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(It.IsAny(), It.IsAny())) .ThrowsAsync(new OperationCanceledException()); // Act var act = async () => await _handler.Handle(command, cts.Token); // Assert await act.Should().ThrowAsync(); } }