Files
Ho Ngoc Hai 76d75c753b Migrate
2026-05-23 18:37:02 +07:00

489 lines
14 KiB
C#

using Xunit;
using FluentAssertions;
using IamService.Domain.AggregatesModel.AccessRequestAggregate;
using IamService.Domain.Events;
namespace IamService.UnitTests.Domain.AccessControl;
/// <summary>
/// EN: Unit tests for AccessRequest aggregate root.
/// VI: Unit tests cho AccessRequest aggregate root.
/// </summary>
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<ArgumentException>()
.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<ArgumentException>()
.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<ArgumentException>()
.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<ArgumentException>()
.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<AccessRequestCreatedEvent>();
}
#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<InvalidOperationException>()
.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<InvalidOperationException>()
.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<InvalidOperationException>()
.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<AccessRequestSubmittedEvent>();
}
#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<InvalidOperationException>()
.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<InvalidOperationException>()
.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<AccessRequestRejectedEvent>();
}
#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<InvalidOperationException>()
.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<InvalidOperationException>()
.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<InvalidOperationException>()
.WithMessage("*submitted request*");
}
#endregion
}