Files
ColaFlow/colaflow-api/tests/Modules/Identity/ColaFlow.Modules.Identity.Domain.Tests/Aggregates/InvitationTests.cs
Yaojia Wang 172d0de1fe
Some checks failed
Code Coverage / Generate Coverage Report (push) Has been cancelled
Tests / Run Tests (9.0.x) (push) Has been cancelled
Tests / Docker Build Test (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Add test
2025-11-04 00:20:42 +01:00

324 lines
9.4 KiB
C#

using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations;
using ColaFlow.Modules.Identity.Domain.Aggregates.Invitations.Events;
using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants;
using ColaFlow.Modules.Identity.Domain.Aggregates.Users;
using FluentAssertions;
using Xunit;
namespace ColaFlow.Modules.Identity.Domain.Tests.Aggregates;
public sealed class InvitationTests
{
private readonly TenantId _tenantId = TenantId.CreateUnique();
private readonly UserId _invitedBy = UserId.CreateUnique();
private const string TestEmail = "invite@example.com";
private const string TestTokenHash = "hashed_token_value";
[Fact]
public void Create_WithValidData_ShouldSucceed()
{
// Arrange & Act
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
// Assert
invitation.Should().NotBeNull();
invitation.Id.Should().NotBeNull();
invitation.TenantId.Should().Be(_tenantId);
invitation.Email.Should().Be(TestEmail.ToLowerInvariant());
invitation.Role.Should().Be(TenantRole.TenantMember);
invitation.TokenHash.Should().Be(TestTokenHash);
invitation.InvitedBy.Should().Be(_invitedBy);
invitation.ExpiresAt.Should().BeCloseTo(DateTime.UtcNow.AddDays(7), TimeSpan.FromSeconds(1));
invitation.AcceptedAt.Should().BeNull();
invitation.IsPending.Should().BeTrue();
invitation.IsExpired.Should().BeFalse();
invitation.IsAccepted.Should().BeFalse();
}
[Fact]
public void Create_WithTenantOwnerRole_ShouldThrowException()
{
// Arrange & Act
var act = () => Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantOwner,
TestTokenHash,
_invitedBy);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Cannot invite users with role TenantOwner*");
}
[Fact]
public void Create_WithAIAgentRole_ShouldThrowException()
{
// Arrange & Act
var act = () => Invitation.Create(
_tenantId,
TestEmail,
TenantRole.AIAgent,
TestTokenHash,
_invitedBy);
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Cannot invite users with role AIAgent*");
}
[Fact]
public void Create_WithEmptyEmail_ShouldThrowException()
{
// Arrange & Act
var act = () => Invitation.Create(
_tenantId,
string.Empty,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Email cannot be empty*");
}
[Fact]
public void Create_WithEmptyTokenHash_ShouldThrowException()
{
// Arrange & Act
var act = () => Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
string.Empty,
_invitedBy);
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*Token hash cannot be empty*");
}
[Fact]
public void Create_ShouldRaiseUserInvitedEvent()
{
// Arrange & Act
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantAdmin,
TestTokenHash,
_invitedBy);
// Assert
invitation.DomainEvents.Should().ContainSingle();
invitation.DomainEvents.Should().ContainItemsAssignableTo<UserInvitedEvent>();
var domainEvent = invitation.DomainEvents.First() as UserInvitedEvent;
domainEvent.Should().NotBeNull();
domainEvent!.InvitationId.Should().Be(invitation.Id);
domainEvent.TenantId.Should().Be(_tenantId);
domainEvent.Email.Should().Be(TestEmail.ToLowerInvariant());
domainEvent.Role.Should().Be(TenantRole.TenantAdmin);
domainEvent.InvitedBy.Should().Be(_invitedBy);
}
[Fact]
public void Accept_WhenPending_ShouldMarkAccepted()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.ClearDomainEvents();
// Act
invitation.Accept();
// Assert
invitation.IsAccepted.Should().BeTrue();
invitation.IsPending.Should().BeFalse();
invitation.AcceptedAt.Should().NotBeNull();
invitation.AcceptedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
invitation.DomainEvents.Should().ContainSingle();
invitation.DomainEvents.Should().ContainItemsAssignableTo<InvitationAcceptedEvent>();
}
[Fact]
public void Accept_WhenExpired_ShouldThrowException()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
// Force expiration by canceling
invitation.Cancel();
// Act
var act = () => invitation.Accept();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Invitation has expired*");
}
[Fact]
public void Accept_WhenAlreadyAccepted_ShouldThrowException()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.Accept();
// Act
var act = () => invitation.Accept();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Invitation has already been accepted*");
}
[Fact]
public void Cancel_WhenPending_ShouldMarkCancelled()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.ClearDomainEvents();
// Act
invitation.Cancel();
// Assert
invitation.IsExpired.Should().BeTrue();
invitation.IsPending.Should().BeFalse();
invitation.ExpiresAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1));
invitation.DomainEvents.Should().ContainSingle();
invitation.DomainEvents.Should().ContainItemsAssignableTo<InvitationCancelledEvent>();
}
[Fact]
public void Cancel_WhenAccepted_ShouldThrowException()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.Accept();
// Act
var act = () => invitation.Cancel();
// Assert
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Cannot cancel non-pending invitation*");
}
[Fact]
public void IsPending_WithPendingInvitation_ShouldReturnTrue()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
// Act & Assert
invitation.IsPending.Should().BeTrue();
invitation.IsExpired.Should().BeFalse();
invitation.IsAccepted.Should().BeFalse();
}
[Fact]
public void IsPending_WithAcceptedInvitation_ShouldReturnFalse()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.Accept();
// Act & Assert
invitation.IsPending.Should().BeFalse();
invitation.IsAccepted.Should().BeTrue();
}
[Fact]
public void ValidateForAcceptance_WithValidInvitation_ShouldNotThrow()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
// Act & Assert
var act = () => invitation.ValidateForAcceptance();
act.Should().NotThrow();
}
[Fact]
public void ValidateForAcceptance_WithExpiredInvitation_ShouldThrow()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.Cancel(); // Force expiration
// Act & Assert
var act = () => invitation.ValidateForAcceptance();
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Invitation has expired*");
}
[Fact]
public void ValidateForAcceptance_WithAcceptedInvitation_ShouldThrow()
{
// Arrange
var invitation = Invitation.Create(
_tenantId,
TestEmail,
TenantRole.TenantMember,
TestTokenHash,
_invitedBy);
invitation.Accept();
// Act & Assert
var act = () => invitation.ValidateForAcceptance();
act.Should().Throw<InvalidOperationException>()
.WithMessage("*Invitation has already been accepted*");
}
}