489 lines
14 KiB
C#
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
|
|
}
|