using Xunit; using FluentAssertions; using IamService.Domain.AggregatesModel.AccessRequestAggregate; using IamService.Domain.Events; namespace IamService.UnitTests.Domain.AccessControl; /// /// EN: Unit tests for AccessRequest aggregate root. /// VI: Unit tests cho AccessRequest aggregate root. /// public class AccessRequestTests { private readonly Guid _validRequesterId = Guid.NewGuid(); private readonly Guid _validResourceId = Guid.NewGuid(); #region Creation Tests [Fact] public void Create_ValidParameters_CreatesAccessRequestInDraftStatus() { // Arrange & Act var request = AccessRequest.Create( _validRequesterId, "Project", _validResourceId, "read", "Need access to project files"); // Assert request.Should().NotBeNull(); request.Id.Should().NotBeEmpty(); request.RequesterId.Should().Be(_validRequesterId); request.ResourceType.Should().Be("Project"); request.ResourceId.Should().Be(_validResourceId); request.RequestedPermission.Should().Be("read"); request.Justification.Should().Be("Need access to project files"); request.Status.Should().Be(AccessRequestStatus.Draft); request.Priority.Should().Be(AccessRequestPriority.Medium); request.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); request.Approvers.Should().BeEmpty(); } [Fact] public void Create_WithCustomPriority_SetsPriority() { // Arrange & Act var request = AccessRequest.Create( _validRequesterId, "Project", _validResourceId, "admin", priority: AccessRequestPriority.High); // Assert request.Priority.Should().Be(AccessRequestPriority.High); } [Fact] public void Create_EmptyRequesterId_ThrowsArgumentException() { // Arrange & Act var act = () => AccessRequest.Create(Guid.Empty, "Project", _validResourceId, "read"); // Assert act.Should().Throw() .WithMessage("*Requester ID*empty*"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Create_InvalidResourceType_ThrowsArgumentException(string? resourceType) { // Arrange & Act var act = () => AccessRequest.Create(_validRequesterId, resourceType!, _validResourceId, "read"); // Assert act.Should().Throw() .WithMessage("*Resource type*empty*"); } [Fact] public void Create_EmptyResourceId_ThrowsArgumentException() { // Arrange & Act var act = () => AccessRequest.Create(_validRequesterId, "Project", Guid.Empty, "read"); // Assert act.Should().Throw() .WithMessage("*Resource ID*empty*"); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void Create_InvalidPermission_ThrowsArgumentException(string? permission) { // Arrange & Act var act = () => AccessRequest.Create(_validRequesterId, "Project", _validResourceId, permission!); // Assert act.Should().Throw() .WithMessage("*permission*empty*"); } [Fact] public void Create_RaisesAccessRequestCreatedEvent() { // Arrange & Act var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); // Assert request.DomainEvents.Should().ContainSingle(); request.DomainEvents.First().Should().BeOfType(); } #endregion #region AddApprover Tests [Fact] public void AddApprover_InDraftStatus_AddsApprover() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); // Act var approver = request.AddApprover(approverId); // Assert request.Approvers.Should().HaveCount(1); approver.UserId.Should().Be(approverId); approver.Order.Should().Be(1); } [Fact] public void AddApprover_MultipleApprovers_SetsCorrectOrder() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approver1 = Guid.NewGuid(); var approver2 = Guid.NewGuid(); // Act request.AddApprover(approver1); request.AddApprover(approver2); // Assert request.Approvers.Should().HaveCount(2); request.Approvers.ElementAt(0).Order.Should().Be(1); request.Approvers.ElementAt(1).Order.Should().Be(2); } [Fact] public void AddApprover_AfterSubmit_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act var act = () => request.AddApprover(Guid.NewGuid()); // Assert act.Should().Throw() .WithMessage("*after request is submitted*"); } #endregion #region Submit Tests [Fact] public void Submit_WithApprovers_ChangeStatusToPending() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); // Act request.Submit(); // Assert request.Status.Should().Be(AccessRequestStatus.Pending); request.SubmittedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); request.ExpiresAt.Should().NotBeNull(); } [Fact] public void Submit_WithoutApprovers_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); // Act var act = () => request.Submit(); // Assert act.Should().Throw() .WithMessage("*At least one approver*"); } [Fact] public void Submit_NotInDraft_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act - Try to submit again var act = () => request.Submit(); // Assert act.Should().Throw() .WithMessage("*draft requests*"); } [Fact] public void Submit_RaisesAccessRequestSubmittedEvent() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.ClearDomainEvents(); // Act request.Submit(); // Assert request.DomainEvents.Should().ContainSingle(); request.DomainEvents.First().Should().BeOfType(); } #endregion #region Approve Tests [Fact] public void Approve_SingleApprover_ChangesStatusToApproved() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); request.AddApprover(approverId); request.Submit(); // Act request.Approve(approverId, "Looks good"); // Assert request.Status.Should().Be(AccessRequestStatus.Approved); request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void Approve_AllApprovers_ChangesStatusToApproved() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approver1 = Guid.NewGuid(); var approver2 = Guid.NewGuid(); request.AddApprover(approver1); request.AddApprover(approver2); request.Submit(); // Act request.Approve(approver1); request.Approve(approver2); // Assert request.Status.Should().Be(AccessRequestStatus.Approved); } [Fact] public void Approve_PartialApproval_StatusRemainsPending() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approver1 = Guid.NewGuid(); var approver2 = Guid.NewGuid(); request.AddApprover(approver1); request.AddApprover(approver2); request.Submit(); // Act request.Approve(approver1); // Assert request.Status.Should().Be(AccessRequestStatus.Pending); } [Fact] public void Approve_NotPending_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); request.AddApprover(approverId); // Not submitted yet // Act var act = () => request.Approve(approverId); // Assert act.Should().Throw() .WithMessage("*pending requests*"); } [Fact] public void Approve_NotAnApprover_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act - Random user trying to approve var act = () => request.Approve(Guid.NewGuid()); // Assert act.Should().Throw() .WithMessage("*not a pending approver*"); } #endregion #region Reject Tests [Fact] public void Reject_PendingRequest_ChangesStatusToRejected() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); request.AddApprover(approverId); request.Submit(); // Act request.Reject(approverId, "Not justified"); // Assert request.Status.Should().Be(AccessRequestStatus.Rejected); request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void Reject_RaisesAccessRequestRejectedEvent() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); request.AddApprover(approverId); request.Submit(); request.ClearDomainEvents(); // Act request.Reject(approverId, "Not justified"); // Assert request.DomainEvents.Should().ContainSingle(); request.DomainEvents.First().Should().BeOfType(); } #endregion #region Cancel Tests [Fact] public void Cancel_DraftRequest_ChangesStatusToCancelled() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); // Act request.Cancel(); // Assert request.Status.Should().Be(AccessRequestStatus.Cancelled); } [Fact] public void Cancel_PendingRequest_ChangesStatusToCancelled() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act request.Cancel(); // Assert request.Status.Should().Be(AccessRequestStatus.Cancelled); } [Fact] public void Cancel_ApprovedRequest_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); var approverId = Guid.NewGuid(); request.AddApprover(approverId); request.Submit(); request.Approve(approverId); // Act var act = () => request.Cancel(); // Assert act.Should().Throw() .WithMessage("*terminal request*"); } #endregion #region Expire Tests [Fact] public void Expire_PendingRequest_ChangesStatusToExpired() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act request.Expire(); // Assert request.Status.Should().Be(AccessRequestStatus.Expired); request.ResolvedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void Expire_NotPending_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); // Act var act = () => request.Expire(); // Assert act.Should().Throw() .WithMessage("*pending requests*"); } #endregion #region UpdateJustification Tests [Fact] public void UpdateJustification_InDraft_UpdatesJustification() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); // Act request.UpdateJustification("Updated justification"); // Assert request.Justification.Should().Be("Updated justification"); } [Fact] public void UpdateJustification_AfterSubmit_ThrowsInvalidOperationException() { // Arrange var request = AccessRequest.Create(_validRequesterId, "Project", _validResourceId, "read"); request.AddApprover(Guid.NewGuid()); request.Submit(); // Act var act = () => request.UpdateJustification("Updated"); // Assert act.Should().Throw() .WithMessage("*submitted request*"); } #endregion }