Files
pos-system/services/iam-service-net/tests/IamService.UnitTests/Domain/AccessControl/AccessReviewTests.cs

403 lines
10 KiB
C#

using Xunit;
using FluentAssertions;
using IamService.Domain.AggregatesModel.AccessReviewAggregate;
using IamService.Domain.Events;
namespace IamService.UnitTests.Domain.AccessControl;
/// <summary>
/// EN: Unit tests for AccessReview aggregate root.
/// VI: Unit tests cho AccessReview aggregate root.
/// </summary>
public class AccessReviewTests
{
private readonly Guid _validOwnerId = Guid.NewGuid();
#region Creation Tests
[Fact]
public void Create_ValidParameters_CreatesAccessReviewInDraftStatus()
{
// Arrange
var dueDate = DateTime.UtcNow.AddDays(7);
// Act
var review = AccessReview.Create(
"Q1 Access Review",
"Quarterly access certification",
_validOwnerId,
"Organization:org-123",
dueDate);
// Assert
review.Should().NotBeNull();
review.Id.Should().NotBeEmpty();
review.Name.Should().Be("Q1 Access Review");
review.Description.Should().Be("Quarterly access certification");
review.OwnerId.Should().Be(_validOwnerId);
review.Scope.Should().Be("Organization:org-123");
review.Status.Should().Be(AccessReviewStatus.Draft);
review.DueDate.Should().Be(dueDate);
review.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
review.Items.Should().BeEmpty();
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void Create_InvalidName_ThrowsArgumentException(string? name)
{
// Arrange & Act
var act = () => AccessReview.Create(
name!,
null,
_validOwnerId,
"Role:admin",
DateTime.UtcNow.AddDays(7));
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Name*empty*");
}
[Fact]
public void Create_EmptyOwnerId_ThrowsArgumentException()
{
// Arrange & Act
var act = () => AccessReview.Create(
"Test Review",
null,
Guid.Empty,
"Role:admin",
DateTime.UtcNow.AddDays(7));
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Owner ID*empty*");
}
[Fact]
public void Create_PastDueDate_ThrowsArgumentException()
{
// Arrange & Act
var act = () => AccessReview.Create(
"Test Review",
null,
_validOwnerId,
"Role:admin",
DateTime.UtcNow.AddDays(-1));
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Due date*future*");
}
[Fact]
public void Create_RaisesAccessReviewCreatedEvent()
{
// Act
var review = AccessReview.Create(
"Test Review",
null,
_validOwnerId,
"Role:admin",
DateTime.UtcNow.AddDays(7));
// Assert
review.DomainEvents.Should().ContainSingle();
review.DomainEvents.First().Should().BeOfType<AccessReviewCreatedEvent>();
}
#endregion
#region AddItem Tests
[Fact]
public void AddItem_InDraftStatus_AddsReviewItem()
{
// Arrange
var review = CreateDraftReview();
var userId = Guid.NewGuid();
var resourceId = Guid.NewGuid();
// Act
var item = review.AddItem(userId, "Project", resourceId, "write");
// Assert
review.Items.Should().HaveCount(1);
item.UserId.Should().Be(userId);
item.ResourceType.Should().Be("Project");
item.Permission.Should().Be("write");
}
[Fact]
public void AddItem_AfterStart_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateDraftReview();
review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
review.Start();
// Act
var act = () => review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "write");
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*draft reviews*");
}
#endregion
#region Start Tests
[Fact]
public void Start_WithItems_ChangesStatusToActive()
{
// Arrange
var review = CreateDraftReview();
review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
// Act
review.Start();
// Assert
review.Status.Should().Be(AccessReviewStatus.Active);
review.StartedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
}
[Fact]
public void Start_WithoutItems_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateDraftReview();
// Act
var act = () => review.Start();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*at least one item*");
}
[Fact]
public void Start_NotInDraft_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateDraftReview();
review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
review.Start();
// Act
var act = () => review.Start();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*draft reviews*");
}
[Fact]
public void Start_RaisesAccessReviewStartedEvent()
{
// Arrange
var review = CreateDraftReview();
review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
review.ClearDomainEvents();
// Act
review.Start();
// Assert
review.DomainEvents.Should().ContainSingle();
review.DomainEvents.First().Should().BeOfType<AccessReviewStartedEvent>();
}
#endregion
#region ReviewItem Tests
[Fact]
public void ReviewItem_Certify_SetsDecisionToCertify()
{
// Arrange
var review = CreateActiveReviewWithItem(out var item);
var reviewerId = Guid.NewGuid();
// Act
review.ReviewItem(item.Id, reviewerId, certify: true, "Approved access");
// Assert
item.Decision.Should().Be(ReviewDecision.Certify);
item.ReviewedByUserId.Should().Be(reviewerId);
}
[Fact]
public void ReviewItem_Revoke_SetsDecisionToRevoke()
{
// Arrange
var review = CreateActiveReviewWithItem(out var item);
var reviewerId = Guid.NewGuid();
// Act
review.ReviewItem(item.Id, reviewerId, certify: false, "Access no longer needed");
// Assert
item.Decision.Should().Be(ReviewDecision.Revoke);
}
[Fact]
public void ReviewItem_NotActive_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateDraftReview();
review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
// Act
var act = () => review.ReviewItem(Guid.NewGuid(), Guid.NewGuid(), true);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*active reviews*");
}
[Fact]
public void ReviewItem_InvalidItemId_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateActiveReviewWithItem(out _);
// Act
var act = () => review.ReviewItem(Guid.NewGuid(), Guid.NewGuid(), true);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Item not found*");
}
#endregion
#region Complete Tests
[Fact]
public void Complete_AllItemsReviewed_ChangesStatusToCompleted()
{
// Arrange
var review = CreateActiveReviewWithItem(out var item);
review.ReviewItem(item.Id, Guid.NewGuid(), true);
// Act
review.Complete();
// Assert
review.Status.Should().Be(AccessReviewStatus.Completed);
review.CompletedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
}
[Fact]
public void Complete_PendingItems_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateActiveReviewWithItem(out _);
// Act
var act = () => review.Complete();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*pending items*");
}
[Fact]
public void Complete_NotActive_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateDraftReview();
// Act
var act = () => review.Complete();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*active reviews*");
}
[Fact]
public void Complete_RaisesAccessReviewCompletedEvent()
{
// Arrange
var review = CreateActiveReviewWithItem(out var item);
review.ReviewItem(item.Id, Guid.NewGuid(), true);
review.ClearDomainEvents();
// Act
review.Complete();
// Assert
review.DomainEvents.Should().ContainSingle();
review.DomainEvents.First().Should().BeOfType<AccessReviewCompletedEvent>();
}
#endregion
#region Cancel Tests
[Fact]
public void Cancel_DraftReview_ChangesStatusToCancelled()
{
// Arrange
var review = CreateDraftReview();
// Act
review.Cancel();
// Assert
review.Status.Should().Be(AccessReviewStatus.Cancelled);
}
[Fact]
public void Cancel_ActiveReview_ChangesStatusToCancelled()
{
// Arrange
var review = CreateActiveReviewWithItem(out _);
// Act
review.Cancel();
// Assert
review.Status.Should().Be(AccessReviewStatus.Cancelled);
}
[Fact]
public void Cancel_CompletedReview_ThrowsInvalidOperationException()
{
// Arrange
var review = CreateActiveReviewWithItem(out var item);
review.ReviewItem(item.Id, Guid.NewGuid(), true);
review.Complete();
// Act
var act = () => review.Cancel();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*completed review*");
}
#endregion
#region Helper Methods
private AccessReview CreateDraftReview() =>
AccessReview.Create("Test Review", null, _validOwnerId, "Role:admin", DateTime.UtcNow.AddDays(7));
private AccessReview CreateActiveReviewWithItem(out AccessReviewItem item)
{
var review = CreateDraftReview();
item = review.AddItem(Guid.NewGuid(), "Project", Guid.NewGuid(), "read");
review.Start();
return review;
}
#endregion
}