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 RemoveGroupMemberCommandHandler. /// VI: Unit tests cho RemoveGroupMemberCommandHandler. /// public class RemoveGroupMemberCommandHandlerTests { private readonly Mock _groupRepositoryMock; private readonly Mock> _loggerMock; private readonly Mock _unitOfWorkMock; private readonly RemoveGroupMemberCommandHandler _handler; public RemoveGroupMemberCommandHandlerTests() { _groupRepositoryMock = new Mock(); _loggerMock = new Mock>(); _unitOfWorkMock = new Mock(); _unitOfWorkMock .Setup(u => u.SaveEntitiesAsync(It.IsAny())) .ReturnsAsync(true); _groupRepositoryMock .Setup(r => r.UnitOfWork) .Returns(_unitOfWorkMock.Object); _handler = new RemoveGroupMemberCommandHandler( _groupRepositoryMock.Object, _loggerMock.Object); } [Fact] public async Task Handle_ValidCommand_RemovesMemberFromGroup() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); group.AddMember(userId); var command = new RemoveGroupMemberCommand(groupId, userId); _groupRepositoryMock .Setup(r => r.GetByIdWithMembersAsync(groupId, It.IsAny())) .ReturnsAsync(group); // Act var result = await _handler.Handle(command, CancellationToken.None); // Assert result.Should().BeTrue(); group.Members.Should().NotContain(m => m.UserId == userId); } [Fact] public async Task Handle_ValidCommand_PersistsChanges() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); group.AddMember(userId); var command = new RemoveGroupMemberCommand(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 RemoveGroupMemberCommand(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_MemberNotInGroup_ThrowsInvalidOperationException() { // Arrange var groupId = Guid.NewGuid(); var userId = Guid.NewGuid(); var group = Group.Create(Guid.NewGuid(), "Test Group"); // NOT adding userId to group var command = new RemoveGroupMemberCommand(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("*not a member*"); } [Fact] public async Task Handle_CancellationRequested_PropagatesCancellation() { // Arrange var command = new RemoveGroupMemberCommand(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(); } }