using ColaFlow.Modules.Identity.Domain.Aggregates.Tenants; using ColaFlow.Modules.Identity.Domain.Aggregates.Users; using ColaFlow.Modules.Identity.Domain.Aggregates.Users.Events; using FluentAssertions; using Xunit; namespace ColaFlow.Modules.Identity.Domain.Tests.Aggregates; public sealed class UserTests { private readonly TenantId _tenantId = TenantId.CreateUnique(); [Fact] public void CreateLocal_ShouldSucceed() { // Arrange var email = Email.Create("test@example.com"); var fullName = FullName.Create("John Doe"); var passwordHash = "hashed_password"; // Act var user = User.CreateLocal(_tenantId, email, passwordHash, fullName); // Assert user.Should().NotBeNull(); user.Id.Should().NotBe(Guid.Empty); user.TenantId.Should().Be(_tenantId); user.Email.Value.Should().Be("test@example.com"); user.FullName.Value.Should().Be("John Doe"); user.PasswordHash.Should().Be(passwordHash); user.Status.Should().Be(UserStatus.Active); user.AuthProvider.Should().Be(AuthenticationProvider.Local); user.CreatedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void CreateLocal_ShouldRaiseUserCreatedEvent() { // Arrange var email = Email.Create("test@example.com"); var fullName = FullName.Create("John Doe"); // Act var user = User.CreateLocal(_tenantId, email, "password", fullName); // Assert user.DomainEvents.Should().ContainSingle(); user.DomainEvents.Should().ContainItemsAssignableTo(); var domainEvent = user.DomainEvents.First() as UserCreatedEvent; domainEvent.Should().NotBeNull(); domainEvent!.UserId.Should().Be(user.Id); domainEvent.Email.Should().Be("test@example.com"); domainEvent.TenantId.Should().Be(_tenantId); } [Fact] public void CreateFromSso_ShouldSucceed() { // Arrange var email = Email.Create("user@company.com"); var fullName = FullName.Create("Jane Smith"); var externalUserId = "google-12345"; var avatarUrl = "https://example.com/avatar.jpg"; // Act var user = User.CreateFromSso( _tenantId, AuthenticationProvider.Google, externalUserId, email, fullName, avatarUrl); // Assert user.Should().NotBeNull(); user.TenantId.Should().Be(_tenantId); user.AuthProvider.Should().Be(AuthenticationProvider.Google); user.ExternalUserId.Should().Be(externalUserId); user.ExternalEmail.Should().Be("user@company.com"); user.AvatarUrl.Should().Be(avatarUrl); user.PasswordHash.Should().BeEmpty(); user.EmailVerifiedAt.Should().NotBeNull(); // SSO users are auto-verified } [Fact] public void CreateFromSso_ShouldRaiseUserCreatedFromSsoEvent() { // Arrange var email = Email.Create("user@company.com"); var fullName = FullName.Create("Jane Smith"); // Act var user = User.CreateFromSso( _tenantId, AuthenticationProvider.AzureAD, "azure-123", email, fullName); // Assert user.DomainEvents.Should().ContainSingle(); user.DomainEvents.Should().ContainItemsAssignableTo(); var domainEvent = user.DomainEvents.First() as UserCreatedFromSsoEvent; domainEvent.Should().NotBeNull(); domainEvent!.Provider.Should().Be(AuthenticationProvider.AzureAD); } [Fact] public void CreateFromSso_ShouldThrow_ForLocalProvider() { // Arrange var email = Email.Create("test@example.com"); var fullName = FullName.Create("John Doe"); // Act & Assert var act = () => User.CreateFromSso( _tenantId, AuthenticationProvider.Local, "external-id", email, fullName); act.Should().Throw() .WithMessage("Use CreateLocal for local authentication"); } [Fact] public void UpdatePassword_ShouldSucceed_ForLocalUser() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "old_hash", FullName.Create("John Doe")); user.ClearDomainEvents(); // Act user.UpdatePassword("new_hash"); // Assert user.PasswordHash.Should().Be("new_hash"); user.DomainEvents.Should().ContainSingle(); user.DomainEvents.Should().ContainItemsAssignableTo(); } [Fact] public void UpdatePassword_ShouldThrow_ForSsoUser() { // Arrange var user = User.CreateFromSso( _tenantId, AuthenticationProvider.Google, "google-123", Email.Create("test@example.com"), FullName.Create("John Doe")); // Act & Assert var act = () => user.UpdatePassword("new_hash"); act.Should().Throw() .WithMessage("Cannot change password for SSO users"); } [Fact] public void UpdateProfile_ShouldUpdateAllFields() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); // Act user.UpdateProfile( fullName: FullName.Create("Jane Smith"), avatarUrl: "https://example.com/avatar.jpg", jobTitle: "Software Engineer", phoneNumber: "+1234567890"); // Assert user.FullName.Value.Should().Be("Jane Smith"); user.AvatarUrl.Should().Be("https://example.com/avatar.jpg"); user.JobTitle.Should().Be("Software Engineer"); user.PhoneNumber.Should().Be("+1234567890"); } [Fact] public void RecordLogin_ShouldUpdateLastLoginAt() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); // Act user.RecordLogin(); // Assert user.LastLoginAt.Should().NotBeNull(); user.LastLoginAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); } [Fact] public void VerifyEmail_ShouldSetEmailVerifiedAt() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); // Act user.VerifyEmail(); // Assert user.EmailVerifiedAt.Should().NotBeNull(); user.EmailVerifiedAt.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(1)); user.EmailVerificationToken.Should().BeNull(); } [Fact] public void Suspend_ShouldChangeStatus() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); user.ClearDomainEvents(); // Act user.Suspend("Violation of terms"); // Assert user.Status.Should().Be(UserStatus.Suspended); user.DomainEvents.Should().ContainSingle(); user.DomainEvents.Should().ContainItemsAssignableTo(); } [Fact] public void Reactivate_ShouldChangeStatusToActive() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); user.Suspend("Test"); // Act user.Reactivate(); // Assert user.Status.Should().Be(UserStatus.Active); } [Fact] public void UpdateSsoProfile_ShouldSucceed_ForSsoUser() { // Arrange var user = User.CreateFromSso( _tenantId, AuthenticationProvider.Google, "google-123", Email.Create("old@example.com"), FullName.Create("Old Name")); // Act user.UpdateSsoProfile( "google-456", Email.Create("new@example.com"), FullName.Create("New Name"), "https://new-avatar.jpg"); // Assert user.ExternalUserId.Should().Be("google-456"); user.ExternalEmail.Should().Be("new@example.com"); user.FullName.Value.Should().Be("New Name"); user.AvatarUrl.Should().Be("https://new-avatar.jpg"); } [Fact] public void UpdateSsoProfile_ShouldThrow_ForLocalUser() { // Arrange var user = User.CreateLocal( _tenantId, Email.Create("test@example.com"), "hash", FullName.Create("John Doe")); // Act & Assert var act = () => user.UpdateSsoProfile( "external-id", Email.Create("new@example.com"), FullName.Create("New Name")); act.Should().Throw() .WithMessage("Cannot update SSO profile for local users"); } }