diff --git a/Identity.sln b/Identity.sln index bdd21d9..b021393 100644 --- a/Identity.sln +++ b/Identity.sln @@ -18,6 +18,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Core", "li EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Contracts", "lib\Logitar.Identity.Contracts\Logitar.Identity.Contracts.csproj", "{2B1BB7B6-DE55-41FB-863E-D6D230A833FB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3544F58B-1C4B-46B4-9931-8F9737D46717}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Core.UnitTests", "tests\Logitar.Identity.Core.UnitTests\Logitar.Identity.Core.UnitTests.csproj", "{0427E96B-E9A4-4771-B0FC-1043372FC68E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Tests", "tests\Logitar.Identity.Tests\Logitar.Identity.Tests.csproj", "{E54EBE14-3B81-48B8-8504-B833236F0136}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Infrastructure", "lib\Logitar.Identity.Infrastructure\Logitar.Identity.Infrastructure.csproj", "{AC5F10DD-1467-4674-B71B-26FDA41A0242}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logitar.Identity.Infrastructure.UnitTests", "tests\Logitar.Identity.Infrastructure.UnitTests\Logitar.Identity.Infrastructure.UnitTests.csproj", "{2B60FA82-D67E-4AB6-9FE5-6682F7AD875C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,10 +42,31 @@ Global {2B1BB7B6-DE55-41FB-863E-D6D230A833FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {2B1BB7B6-DE55-41FB-863E-D6D230A833FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {2B1BB7B6-DE55-41FB-863E-D6D230A833FB}.Release|Any CPU.Build.0 = Release|Any CPU + {0427E96B-E9A4-4771-B0FC-1043372FC68E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0427E96B-E9A4-4771-B0FC-1043372FC68E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0427E96B-E9A4-4771-B0FC-1043372FC68E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0427E96B-E9A4-4771-B0FC-1043372FC68E}.Release|Any CPU.Build.0 = Release|Any CPU + {E54EBE14-3B81-48B8-8504-B833236F0136}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E54EBE14-3B81-48B8-8504-B833236F0136}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E54EBE14-3B81-48B8-8504-B833236F0136}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E54EBE14-3B81-48B8-8504-B833236F0136}.Release|Any CPU.Build.0 = Release|Any CPU + {AC5F10DD-1467-4674-B71B-26FDA41A0242}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC5F10DD-1467-4674-B71B-26FDA41A0242}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC5F10DD-1467-4674-B71B-26FDA41A0242}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC5F10DD-1467-4674-B71B-26FDA41A0242}.Release|Any CPU.Build.0 = Release|Any CPU + {2B60FA82-D67E-4AB6-9FE5-6682F7AD875C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B60FA82-D67E-4AB6-9FE5-6682F7AD875C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B60FA82-D67E-4AB6-9FE5-6682F7AD875C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B60FA82-D67E-4AB6-9FE5-6682F7AD875C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0427E96B-E9A4-4771-B0FC-1043372FC68E} = {3544F58B-1C4B-46B4-9931-8F9737D46717} + {E54EBE14-3B81-48B8-8504-B833236F0136} = {3544F58B-1C4B-46B4-9931-8F9737D46717} + {2B60FA82-D67E-4AB6-9FE5-6682F7AD875C} = {3544F58B-1C4B-46B4-9931-8F9737D46717} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {45FD7647-C5AB-4CE1-A93C-59A73FDD2196} EndGlobalSection diff --git a/lib/Logitar.Identity.Contracts/Logitar.Identity.Contracts.csproj b/lib/Logitar.Identity.Contracts/Logitar.Identity.Contracts.csproj index 8291cd6..2992e8a 100644 --- a/lib/Logitar.Identity.Contracts/Logitar.Identity.Contracts.csproj +++ b/lib/Logitar.Identity.Contracts/Logitar.Identity.Contracts.csproj @@ -4,7 +4,7 @@ netstandard2.1 10 enable - true + True Logitar.Identity.Contracts Francis Pion Logitar diff --git a/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs b/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs index 3046343..ebbcf0c 100644 --- a/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs +++ b/lib/Logitar.Identity.Core/ApiKeys/ApiKey.cs @@ -282,12 +282,15 @@ public void SetCustomAttribute(Identifier key, string value) { RemoveCustomAttribute(key); } - value = value.Trim(); - - if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value) + else { - _customAttributes[key] = value; - _updated.CustomAttributes[key] = value; + value = value.Trim(); + + if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value) + { + _customAttributes[key] = value; + _updated.CustomAttributes[key] = value; + } } } @@ -334,4 +337,10 @@ protected virtual void Handle(ApiKeyUpdated @event) } } } + + /// + /// Returns a string representation of the API key. + /// + /// The string representation. + public override string ToString() => $"{DisplayName} | {base.ToString()}"; } diff --git a/lib/Logitar.Identity.Core/ApiKeys/ApiKeyId.cs b/lib/Logitar.Identity.Core/ApiKeys/ApiKeyId.cs index 3dc1365..d371d3f 100644 --- a/lib/Logitar.Identity.Core/ApiKeys/ApiKeyId.cs +++ b/lib/Logitar.Identity.Core/ApiKeys/ApiKeyId.cs @@ -7,6 +7,11 @@ namespace Logitar.Identity.Core.ApiKeys; /// public readonly struct ApiKeyId { + /// + /// The separator between the tenant ID and the entity ID. + /// + private const char Separator = ':'; + /// /// Gets the identifier of the event stream. /// @@ -30,27 +35,31 @@ public readonly struct ApiKeyId /// /// The tenant identifier. /// The entity identifier. - public ApiKeyId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) - { - } - /// - /// Initializes a new instance of the struct. - /// - /// The tenant identifier. - /// The entity identifier. - public ApiKeyId(TenantId? tenantId, string entityId) + public ApiKeyId(TenantId? tenantId, EntityId entityId) { + StreamId = new(tenantId == null ? entityId.Value : string.Join(Separator, tenantId, entityId)); TenantId = tenantId; - EntityId = new(entityId); - StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + EntityId = entityId; } + /// /// Initializes a new instance of the struct. /// - /// A stream identifier. + /// The identifier of the event stream. public ApiKeyId(StreamId streamId) { StreamId = streamId; + + string[] values = streamId.Value.Split(Separator); + if (values.Length > 2) + { + throw new ArgumentException($"The value '{streamId}' is not a valid API key ID.", nameof(streamId)); + } + else if (values.Length == 2) + { + TenantId = new(values.First()); + } + EntityId = new(values.Last()); } /// @@ -58,7 +67,7 @@ public ApiKeyId(StreamId streamId) /// /// The tenant identifier. /// The generated identifier. - public static ApiKeyId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + public static ApiKeyId NewId(TenantId? tenantId = null) => new(tenantId, EntityId.NewId()); /// /// Returns a value indicating whether or not the specified identifiers are equal. diff --git a/lib/Logitar.Identity.Core/DependencyInjectionExtensions.cs b/lib/Logitar.Identity.Core/DependencyInjectionExtensions.cs index dcfdef4..c87f2de 100644 --- a/lib/Logitar.Identity.Core/DependencyInjectionExtensions.cs +++ b/lib/Logitar.Identity.Core/DependencyInjectionExtensions.cs @@ -1,4 +1,5 @@ -using Logitar.Identity.Core.Roles; +using Logitar.EventSourcing; +using Logitar.Identity.Core.Roles; using Logitar.Identity.Core.Settings; using Logitar.Identity.Core.Users; using Microsoft.Extensions.DependencyInjection; @@ -18,6 +19,7 @@ public static class DependencyInjectionExtensions public static IServiceCollection AddLogitarIdentityCore(this IServiceCollection services) { return services + .AddLogitarEventSourcing() .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj b/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj index fd60e61..780e0b0 100644 --- a/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj +++ b/lib/Logitar.Identity.Core/Logitar.Identity.Core.csproj @@ -3,7 +3,7 @@ net9.0 enable - true + True Logitar.Identity.Core Francis Pion Logitar diff --git a/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs index 7da32c9..f80eab3 100644 --- a/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePassword.cs @@ -1,5 +1,4 @@ using Logitar.EventSourcing; -using Logitar.Identity.Core.ApiKeys; using Logitar.Identity.Core.Passwords.Events; namespace Logitar.Identity.Core.Passwords; @@ -23,7 +22,7 @@ public class OneTimePassword : AggregateRoot /// /// Gets the identifier of the One-Time Password (OTP). /// - public new ApiKeyId Id => new(base.Id); + public new OneTimePasswordId Id => new(base.Id); /// /// Gets the tenant identifier of the One-Time Password (OTP). /// diff --git a/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs index 34388d1..d093670 100644 --- a/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs +++ b/lib/Logitar.Identity.Core/Passwords/OneTimePasswordId.cs @@ -7,6 +7,11 @@ namespace Logitar.Identity.Core.Passwords; /// public readonly struct OneTimePasswordId { + /// + /// The separator between the tenant ID and the entity ID. + /// + private const char Separator = ':'; + /// /// Gets the identifier of the event stream. /// @@ -30,35 +35,39 @@ public readonly struct OneTimePasswordId /// /// The tenant identifier. /// The entity identifier. - public OneTimePasswordId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) - { - } - /// - /// Initializes a new instance of the struct. - /// - /// The tenant identifier. - /// The entity identifier. - public OneTimePasswordId(TenantId? tenantId, string entityId) + public OneTimePasswordId(TenantId? tenantId, EntityId entityId) { + StreamId = new(tenantId == null ? entityId.Value : string.Join(Separator, tenantId, entityId)); TenantId = tenantId; - EntityId = new(entityId); - StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + EntityId = entityId; } + /// /// Initializes a new instance of the struct. /// - /// A stream identifier. + /// The identifier of the event stream. public OneTimePasswordId(StreamId streamId) { StreamId = streamId; + + string[] values = streamId.Value.Split(Separator); + if (values.Length > 2) + { + throw new ArgumentException($"The value '{streamId}' is not a valid session ID.", nameof(streamId)); + } + else if (values.Length == 2) + { + TenantId = new(values.First()); + } + EntityId = new(values.Last()); } /// - /// Randomly generates a new One-Time Password (OTP) identifier. + /// Randomly generates a new session identifier. /// /// The tenant identifier. /// The generated identifier. - public static OneTimePasswordId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + public static OneTimePasswordId NewId(TenantId? tenantId = null) => new(tenantId, EntityId.NewId()); /// /// Returns a value indicating whether or not the specified identifiers are equal. diff --git a/lib/Logitar.Identity.Core/Roles/Role.cs b/lib/Logitar.Identity.Core/Roles/Role.cs index 257f5b8..d73c858 100644 --- a/lib/Logitar.Identity.Core/Roles/Role.cs +++ b/lib/Logitar.Identity.Core/Roles/Role.cs @@ -147,12 +147,15 @@ public void SetCustomAttribute(Identifier key, string value) { RemoveCustomAttribute(key); } - value = value.Trim(); - - if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value) + else { - _customAttributes[key] = value; - _updated.CustomAttributes[key] = value; + value = value.Trim(); + + if (!_customAttributes.TryGetValue(key, out string? existingValue) || existingValue != value) + { + _customAttributes[key] = value; + _updated.CustomAttributes[key] = value; + } } } @@ -161,7 +164,7 @@ public void SetCustomAttribute(Identifier key, string value) /// /// The unique name. /// The actor identifier. - public void SetUniqueName(UniqueName uniqueName, ActorId? actorId) + public void SetUniqueName(UniqueName uniqueName, ActorId? actorId = null) { if (_uniqueName != uniqueName) { diff --git a/lib/Logitar.Identity.Core/Roles/RoleId.cs b/lib/Logitar.Identity.Core/Roles/RoleId.cs index 6038e29..d38119d 100644 --- a/lib/Logitar.Identity.Core/Roles/RoleId.cs +++ b/lib/Logitar.Identity.Core/Roles/RoleId.cs @@ -7,6 +7,11 @@ namespace Logitar.Identity.Core.Roles; /// public readonly struct RoleId { + /// + /// The separator between the tenant ID and the entity ID. + /// + private const char Separator = ':'; + /// /// Gets the identifier of the event stream. /// @@ -30,27 +35,31 @@ public readonly struct RoleId /// /// The tenant identifier. /// The entity identifier. - public RoleId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) - { - } - /// - /// Initializes a new instance of the struct. - /// - /// The tenant identifier. - /// The entity identifier. - public RoleId(TenantId? tenantId, string entityId) + public RoleId(TenantId? tenantId, EntityId entityId) { + StreamId = new(tenantId == null ? entityId.Value : string.Join(Separator, tenantId, entityId)); TenantId = tenantId; - EntityId = new(entityId); - StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + EntityId = entityId; } + /// /// Initializes a new instance of the struct. /// - /// A stream identifier. + /// The identifier of the event stream. public RoleId(StreamId streamId) { StreamId = streamId; + + string[] values = streamId.Value.Split(Separator); + if (values.Length > 2) + { + throw new ArgumentException($"The value '{streamId}' is not a valid role ID.", nameof(streamId)); + } + else if (values.Length == 2) + { + TenantId = new(values.First()); + } + EntityId = new(values.Last()); } /// @@ -58,7 +67,7 @@ public RoleId(StreamId streamId) /// /// The tenant identifier. /// The generated identifier. - public static RoleId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + public static RoleId NewId(TenantId? tenantId = null) => new(tenantId, EntityId.NewId()); /// /// Returns a value indicating whether or not the specified identifiers are equal. diff --git a/lib/Logitar.Identity.Core/Sessions/SessionId.cs b/lib/Logitar.Identity.Core/Sessions/SessionId.cs index 85fb717..e96a0ac 100644 --- a/lib/Logitar.Identity.Core/Sessions/SessionId.cs +++ b/lib/Logitar.Identity.Core/Sessions/SessionId.cs @@ -7,6 +7,11 @@ namespace Logitar.Identity.Core.Sessions; /// public readonly struct SessionId { + /// + /// The separator between the tenant ID and the entity ID. + /// + private const char Separator = ':'; + /// /// Gets the identifier of the event stream. /// @@ -30,27 +35,31 @@ public readonly struct SessionId /// /// The tenant identifier. /// The entity identifier. - public SessionId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) - { - } - /// - /// Initializes a new instance of the struct. - /// - /// The tenant identifier. - /// The entity identifier. - public SessionId(TenantId? tenantId, string entityId) + public SessionId(TenantId? tenantId, EntityId entityId) { + StreamId = new(tenantId == null ? entityId.Value : string.Join(Separator, tenantId, entityId)); TenantId = tenantId; - EntityId = new(entityId); - StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + EntityId = entityId; } + /// /// Initializes a new instance of the struct. /// - /// A stream identifier. + /// The identifier of the event stream. public SessionId(StreamId streamId) { StreamId = streamId; + + string[] values = streamId.Value.Split(Separator); + if (values.Length > 2) + { + throw new ArgumentException($"The value '{streamId}' is not a valid session ID.", nameof(streamId)); + } + else if (values.Length == 2) + { + TenantId = new(values.First()); + } + EntityId = new(values.Last()); } /// @@ -58,7 +67,7 @@ public SessionId(StreamId streamId) /// /// The tenant identifier. /// The generated identifier. - public static SessionId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + public static SessionId NewId(TenantId? tenantId = null) => new(tenantId, EntityId.NewId()); /// /// Returns a value indicating whether or not the specified identifiers are equal. diff --git a/lib/Logitar.Identity.Core/Users/Address.cs b/lib/Logitar.Identity.Core/Users/Address.cs index 0c2353d..1eca32b 100644 --- a/lib/Logitar.Identity.Core/Users/Address.cs +++ b/lib/Logitar.Identity.Core/Users/Address.cs @@ -45,12 +45,12 @@ public record Address : Contact, IAddress /// The postal code of the address. /// The region of the address. /// A value indicating whether or not the contact is verified. - public Address(IAddressHelper helper, string street, string locality, string country, string? postalCode = null, string? region = null, bool isVerified = false) : base(isVerified) + public Address(IAddressHelper helper, string street, string locality, string country, string? region = null, string? postalCode = null, bool isVerified = false) : base(isVerified) { Street = street.Trim(); Locality = locality.Trim(); - PostalCode = postalCode?.CleanTrim(); Region = region?.CleanTrim(); + PostalCode = postalCode?.CleanTrim(); Country = country.Trim(); new AddressValidator(helper).ValidateAndThrow(this); } diff --git a/lib/Logitar.Identity.Core/Users/UserId.cs b/lib/Logitar.Identity.Core/Users/UserId.cs index 8cd285f..05ac8dd 100644 --- a/lib/Logitar.Identity.Core/Users/UserId.cs +++ b/lib/Logitar.Identity.Core/Users/UserId.cs @@ -7,6 +7,11 @@ namespace Logitar.Identity.Core.Users; /// public readonly struct UserId { + /// + /// The separator between the tenant ID and the entity ID. + /// + private const char Separator = ':'; + /// /// Gets the identifier of the event stream. /// @@ -30,27 +35,31 @@ public readonly struct UserId /// /// The tenant identifier. /// The entity identifier. - public UserId(TenantId? tenantId, Guid entityId) : this(tenantId, Convert.ToBase64String(entityId.ToByteArray()).ToUriSafeBase64()) - { - } - /// - /// Initializes a new instance of the struct. - /// - /// The tenant identifier. - /// The entity identifier. - public UserId(TenantId? tenantId, string entityId) + public UserId(TenantId? tenantId, EntityId entityId) { + StreamId = new(tenantId == null ? entityId.Value : string.Join(Separator, tenantId, entityId)); TenantId = tenantId; - EntityId = new(entityId); - StreamId = new(tenantId.HasValue ? $"{tenantId}:{entityId}" : entityId); + EntityId = entityId; } + /// /// Initializes a new instance of the struct. /// - /// A stream identifier. + /// The identifier of the event stream. public UserId(StreamId streamId) { StreamId = streamId; + + string[] values = streamId.Value.Split(Separator); + if (values.Length > 2) + { + throw new ArgumentException($"The value '{streamId}' is not a valid user ID.", nameof(streamId)); + } + else if (values.Length == 2) + { + TenantId = new(values.First()); + } + EntityId = new(values.Last()); } /// @@ -58,7 +67,7 @@ public UserId(StreamId streamId) /// /// The tenant identifier. /// The generated identifier. - public static UserId NewId(TenantId? tenantId = null) => new(tenantId, Guid.NewGuid()); + public static UserId NewId(TenantId? tenantId = null) => new(tenantId, EntityId.NewId()); /// /// Returns a value indicating whether or not the specified identifiers are equal. diff --git a/lib/Logitar.Identity.Core/Users/UserManager.cs b/lib/Logitar.Identity.Core/Users/UserManager.cs index ee3264d..0879574 100644 --- a/lib/Logitar.Identity.Core/Users/UserManager.cs +++ b/lib/Logitar.Identity.Core/Users/UserManager.cs @@ -75,7 +75,8 @@ public virtual async Task FindAsync(string? tenantIdValue, string id UserId? userId = null; try { - userId = new(tenantId, id); + EntityId entityId = new(id); + userId = new(tenantId, entityId); } catch (Exception) { diff --git a/lib/Logitar.Identity.Core/Validators/LocaleValidator.cs b/lib/Logitar.Identity.Core/Validators/LocaleValidator.cs index 8bf577b..538ca10 100644 --- a/lib/Logitar.Identity.Core/Validators/LocaleValidator.cs +++ b/lib/Logitar.Identity.Core/Validators/LocaleValidator.cs @@ -26,7 +26,7 @@ public class LocaleValidator : IPropertyValidator /// The default error message template. public string GetDefaultMessageTemplate(string errorCode) { - return "'{PropertyName}' must be a valid locale code. It cannot be the invariant culture, nor a user-defined culture."; + return "'{PropertyName}' must be a valid locale code. It cannot be the invariant culture, nor an user-defined culture."; } /// diff --git a/lib/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs new file mode 100644 index 0000000..fe59271 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs @@ -0,0 +1,23 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.ApiKeys; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class ApiKeyIdConverter : JsonConverter +{ + public override ApiKeyId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + return new ApiKeyId(); + } + StreamId streamId = new(value); + return new(streamId); + } + + public override void Write(Utf8JsonWriter writer, ApiKeyId apiKeyId, JsonSerializerOptions options) + { + writer.WriteStringValue(apiKeyId.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/CustomIdentifierConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/CustomIdentifierConverter.cs new file mode 100644 index 0000000..aacea57 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/CustomIdentifierConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class CustomIdentifierConverter : JsonConverter +{ + public override CustomIdentifier? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return CustomIdentifier.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, CustomIdentifier customIdentifier, JsonSerializerOptions options) + { + writer.WriteStringValue(customIdentifier.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs new file mode 100644 index 0000000..5a3177e --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class DescriptionConverter : JsonConverter +{ + public override Description? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Description.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, Description description, JsonSerializerOptions options) + { + writer.WriteStringValue(description.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs new file mode 100644 index 0000000..ec81ab1 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class DisplayNameConverter : JsonConverter +{ + public override DisplayName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DisplayName.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, DisplayName displayName, JsonSerializerOptions options) + { + writer.WriteStringValue(displayName.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/EntityIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/EntityIdConverter.cs new file mode 100644 index 0000000..f049c71 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/EntityIdConverter.cs @@ -0,0 +1,17 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class EntityIdConverter : JsonConverter +{ + public override EntityId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + return string.IsNullOrWhiteSpace(value) ? new EntityId() : new(value); + } + + public override void Write(Utf8JsonWriter writer, EntityId entityId, JsonSerializerOptions options) + { + writer.WriteStringValue(entityId.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs new file mode 100644 index 0000000..1d04a34 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core.Users; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class GenderConverter : JsonConverter +{ + public override Gender? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Gender.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, Gender gender, JsonSerializerOptions options) + { + writer.WriteStringValue(gender.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/IdentifierConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/IdentifierConverter.cs new file mode 100644 index 0000000..f0afaea --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/IdentifierConverter.cs @@ -0,0 +1,26 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class IdentifierConverter : JsonConverter +{ + public override Identifier? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Identifier.TryCreate(reader.GetString()); + } + + public override Identifier ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Read(ref reader, typeToConvert, options) ?? throw new InvalidOperationException("The identifier could not be read."); + } + + public override void Write(Utf8JsonWriter writer, Identifier identifier, JsonSerializerOptions options) + { + writer.WriteStringValue(identifier.Value); + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, [DisallowNull] Identifier identifier, JsonSerializerOptions options) + { + writer.WritePropertyName(identifier.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs new file mode 100644 index 0000000..b00aed5 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class LocaleConverter : JsonConverter +{ + public override Locale? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Locale.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, Locale locale, JsonSerializerOptions options) + { + writer.WriteStringValue(locale.Code); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs new file mode 100644 index 0000000..6292b13 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs @@ -0,0 +1,23 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.Passwords; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class OneTimePasswordIdConverter : JsonConverter +{ + public override OneTimePasswordId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + return new OneTimePasswordId(); + } + StreamId streamId = new(value); + return new(streamId); + } + + public override void Write(Utf8JsonWriter writer, OneTimePasswordId oneTimePasswordId, JsonSerializerOptions options) + { + writer.WriteStringValue(oneTimePasswordId.Value); + } +} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs similarity index 93% rename from old/src/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs rename to lib/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs index 6847434..0b55c89 100644 --- a/old/src/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs +++ b/lib/Logitar.Identity.Infrastructure/Converters/PasswordConverter.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Passwords; +using Logitar.Identity.Core.Passwords; namespace Logitar.Identity.Infrastructure.Converters; diff --git a/lib/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs new file mode 100644 index 0000000..8a7ad56 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core.Users; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class PersonNameConverter : JsonConverter +{ + public override PersonName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return PersonName.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, PersonName personName, JsonSerializerOptions options) + { + writer.WriteStringValue(personName.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs new file mode 100644 index 0000000..f969cc7 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs @@ -0,0 +1,23 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.Roles; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class RoleIdConverter : JsonConverter +{ + public override RoleId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + return new RoleId(); + } + StreamId streamId = new(value); + return new(streamId); + } + + public override void Write(Utf8JsonWriter writer, RoleId roleId, JsonSerializerOptions options) + { + writer.WriteStringValue(roleId.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs new file mode 100644 index 0000000..15a1e96 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs @@ -0,0 +1,23 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.Sessions; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class SessionIdConverter : JsonConverter +{ + public override SessionId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + return new SessionId(); + } + StreamId streamId = new(value); + return new(streamId); + } + + public override void Write(Utf8JsonWriter writer, SessionId sessionId, JsonSerializerOptions options) + { + writer.WriteStringValue(sessionId.Value); + } +} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs similarity index 52% rename from old/src/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs rename to lib/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs index a69813c..1ca6ec0 100644 --- a/old/src/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs +++ b/lib/Logitar.Identity.Infrastructure/Converters/TenantIdConverter.cs @@ -1,12 +1,13 @@ -using Logitar.Identity.Domain.Shared; +using Logitar.Identity.Core; namespace Logitar.Identity.Infrastructure.Converters; public class TenantIdConverter : JsonConverter { - public override TenantId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TenantId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return TenantId.TryCreate(reader.GetString()); + string? value = reader.GetString(); + return string.IsNullOrWhiteSpace(value) ? new TenantId() : new(value); } public override void Write(Utf8JsonWriter writer, TenantId tenantId, JsonSerializerOptions options) diff --git a/lib/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs new file mode 100644 index 0000000..ba0ccff --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs @@ -0,0 +1,16 @@ +using TimeZone = Logitar.Identity.Core.TimeZone; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class TimeZoneConverter : JsonConverter +{ + public override TimeZone? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return TimeZone.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, TimeZone timeZone, JsonSerializerOptions options) + { + writer.WriteStringValue(timeZone.Id); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs new file mode 100644 index 0000000..0981846 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs @@ -0,0 +1,22 @@ +using Logitar.Identity.Core; +using Logitar.Identity.Core.Settings; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class UniqueNameConverter : JsonConverter +{ + private readonly UniqueNameSettings _uniqueNameSettings = new() + { + AllowedCharacters = null // NOTE(fpion): strict validation is not required when deserializing an unique name. + }; + + public override UniqueName? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return UniqueName.TryCreate(reader.GetString(), _uniqueNameSettings); + } + + public override void Write(Utf8JsonWriter writer, UniqueName uniqueName, JsonSerializerOptions options) + { + writer.WriteStringValue(uniqueName.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs new file mode 100644 index 0000000..3e1c5ee --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs @@ -0,0 +1,16 @@ +using Logitar.Identity.Core; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class UrlConverter : JsonConverter +{ + public override Url? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Url.TryCreate(reader.GetString()); + } + + public override void Write(Utf8JsonWriter writer, Url url, JsonSerializerOptions options) + { + writer.WriteStringValue(url.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs b/lib/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs new file mode 100644 index 0000000..59b59d4 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs @@ -0,0 +1,23 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.Users; + +namespace Logitar.Identity.Infrastructure.Converters; + +public class UserIdConverter : JsonConverter +{ + public override UserId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? value = reader.GetString(); + if (string.IsNullOrWhiteSpace(value)) + { + return new UserId(); + } + StreamId streamId = new(value); + return new(streamId); + } + + public override void Write(Utf8JsonWriter writer, UserId userId, JsonSerializerOptions options) + { + writer.WriteStringValue(userId.Value); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs b/lib/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..32d7f2c --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs @@ -0,0 +1,37 @@ +using Logitar.EventSourcing.Infrastructure; +using Logitar.Identity.Core; +using Logitar.Identity.Core.Passwords; +using Logitar.Identity.Core.Tokens; +using Logitar.Identity.Infrastructure.Converters; +using Logitar.Identity.Infrastructure.Passwords; +using Logitar.Identity.Infrastructure.Passwords.Pbkdf2; +using Logitar.Identity.Infrastructure.Tokens; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Logitar.Identity.Infrastructure; + +public static class DependencyInjectionExtensions +{ + public static IServiceCollection AddLogitarIdentityInfrastructure(this IServiceCollection services) + { + return services + .AddLogitarIdentityCore() + .AddPasswordStrategies() + .AddSingleton(serviceProvider => + { + IConfiguration configuration = serviceProvider.GetRequiredService(); + return configuration.GetSection(Pbkdf2Settings.SectionKey).Get() ?? new(); + }) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddScoped() + .AddScoped(); + } + + private static IServiceCollection AddPasswordStrategies(this IServiceCollection services) + { + return services.AddSingleton(); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/EventBus.cs b/lib/Logitar.Identity.Infrastructure/EventBus.cs new file mode 100644 index 0000000..d4dc631 --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/EventBus.cs @@ -0,0 +1,20 @@ +using Logitar.EventSourcing; +using Logitar.EventSourcing.Infrastructure; +using MediatR; + +namespace Logitar.Identity.Infrastructure; + +internal class EventBus : IEventBus +{ + private readonly IMediator _mediator; + + public EventBus(IMediator mediator) + { + _mediator = mediator; + } + + public async Task PublishAsync(IEvent @event, CancellationToken cancellationToken) + { + await _mediator.Publish(@event, cancellationToken); + } +} diff --git a/lib/Logitar.Identity.Infrastructure/EventSerializer.cs b/lib/Logitar.Identity.Infrastructure/EventSerializer.cs new file mode 100644 index 0000000..707b6dd --- /dev/null +++ b/lib/Logitar.Identity.Infrastructure/EventSerializer.cs @@ -0,0 +1,37 @@ +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Infrastructure; + +public class EventSerializer : EventSourcing.Infrastructure.EventSerializer +{ + private readonly PasswordConverter _passwordConverter; + + public EventSerializer(PasswordConverter passwordConverter) + { + _passwordConverter = passwordConverter; + } + + protected override void RegisterConverters() + { + base.RegisterConverters(); + + SerializerOptions.Converters.Add(_passwordConverter); + SerializerOptions.Converters.Add(new ApiKeyIdConverter()); + SerializerOptions.Converters.Add(new CustomIdentifierConverter()); + SerializerOptions.Converters.Add(new DescriptionConverter()); + SerializerOptions.Converters.Add(new DisplayNameConverter()); + SerializerOptions.Converters.Add(new EntityIdConverter()); + SerializerOptions.Converters.Add(new GenderConverter()); + SerializerOptions.Converters.Add(new IdentifierConverter()); + SerializerOptions.Converters.Add(new LocaleConverter()); + SerializerOptions.Converters.Add(new OneTimePasswordIdConverter()); + SerializerOptions.Converters.Add(new PersonNameConverter()); + SerializerOptions.Converters.Add(new RoleIdConverter()); + SerializerOptions.Converters.Add(new SessionIdConverter()); + SerializerOptions.Converters.Add(new TenantIdConverter()); + SerializerOptions.Converters.Add(new TimeZoneConverter()); + SerializerOptions.Converters.Add(new UniqueNameConverter()); + SerializerOptions.Converters.Add(new UrlConverter()); + SerializerOptions.Converters.Add(new UserIdConverter()); + } +} diff --git a/old/src/Logitar.Identity.Infrastructure/LICENSE b/lib/Logitar.Identity.Infrastructure/LICENSE similarity index 100% rename from old/src/Logitar.Identity.Infrastructure/LICENSE rename to lib/Logitar.Identity.Infrastructure/LICENSE diff --git a/old/src/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj b/lib/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj similarity index 78% rename from old/src/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj rename to lib/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj index 05aaa96..32280ba 100644 --- a/old/src/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj +++ b/lib/Logitar.Identity.Infrastructure/Logitar.Identity.Infrastructure.csproj @@ -1,10 +1,10 @@  - net8.0 + net9.0 enable enable - true + True Logitar.Identity.Infrastructure Francis Pion Logitar @@ -15,16 +15,16 @@ README.md https://github.com/Logitar/Identity git - 2.0.0.0 + 0.0.0.0 $(AssemblyVersion) LICENSE True - 2.0.0 + 0.0.0 en-CA False - Refactored domain events and aggregates. + Rewrote the framework to include changes made to EventSourcing. logitar;net;framework;identity;infrastructure - https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.Infrastructure + https://github.com/Logitar/Identity/tree/main/lib/Logitar.Identity.Infrastructure @@ -36,32 +36,18 @@ - - \ - True - - - \ - True - - - \ - True - - - - - - - - + + + + - + + @@ -70,4 +56,19 @@ + + + \ + True + + + \ + True + + + \ + True + + + diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs b/lib/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs similarity index 94% rename from old/src/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs index 9952d63..214807f 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/IPasswordStrategy.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Passwords; +using Logitar.Identity.Core.Passwords; namespace Logitar.Identity.Infrastructure.Passwords; diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs b/lib/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs similarity index 95% rename from old/src/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs index 36b5c8b..6961e8e 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/PasswordManager.cs @@ -1,8 +1,6 @@ -using FluentValidation; -using Logitar.Identity.Contracts.Settings; -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Passwords.Validators; -using Logitar.Identity.Domain.Settings; +using Logitar.Identity.Contracts.Settings; +using Logitar.Identity.Core.Passwords; +using Logitar.Identity.Core.Settings; using Logitar.Security.Cryptography; namespace Logitar.Identity.Infrastructure.Passwords; @@ -120,7 +118,7 @@ public virtual void Validate(string password) public virtual void Validate(string password, IPasswordSettings? passwordSettings) { passwordSettings ??= SettingsResolver.Resolve().Password; - new PasswordValidator(passwordSettings).ValidateAndThrow(password); + //new PasswordValidator(passwordSettings).ValidateAndThrow(password); // TODO(fpion): complete } /// diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs b/lib/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs similarity index 85% rename from old/src/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs index 9cc68b8..72d1593 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/PasswordStrategyNotSupportedException.cs @@ -2,7 +2,7 @@ public class PasswordStrategyNotSupportedException : NotSupportedException { - public const string ErrorMessage = "The specified password strategy is not supported."; + private const string ErrorMessage = "The specified password strategy is not supported."; public string Strategy { diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs similarity index 97% rename from old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs index 966d8c0..29708ef 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Password.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Passwords; +using Logitar.Identity.Core.Passwords; using Microsoft.AspNetCore.Cryptography.KeyDerivation; namespace Logitar.Identity.Infrastructure.Passwords.Pbkdf2; diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs similarity index 88% rename from old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs index 9598bb7..ab3303e 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Settings.cs @@ -4,6 +4,8 @@ namespace Logitar.Identity.Infrastructure.Passwords.Pbkdf2; public record Pbkdf2Settings { + public const string SectionKey = "Pbkdf2"; + public KeyDerivationPrf Algorithm { get; set; } = KeyDerivationPrf.HMACSHA256; public int Iterations { get; set; } = 600000; public int SaltLength { get; set; } = 256 / 8; diff --git a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs similarity index 96% rename from old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs rename to lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs index 031e3e9..6a8cc64 100644 --- a/old/src/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs +++ b/lib/Logitar.Identity.Infrastructure/Passwords/Pbkdf2/Pbkdf2Strategy.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Passwords; +using Logitar.Identity.Core.Passwords; namespace Logitar.Identity.Infrastructure.Passwords.Pbkdf2; diff --git a/old/src/Logitar.Identity.Infrastructure/README.md b/lib/Logitar.Identity.Infrastructure/README.md similarity index 100% rename from old/src/Logitar.Identity.Infrastructure/README.md rename to lib/Logitar.Identity.Infrastructure/README.md diff --git a/old/src/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs b/lib/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs similarity index 99% rename from old/src/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs rename to lib/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs index 96fd0ea..13726cd 100644 --- a/old/src/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs +++ b/lib/Logitar.Identity.Infrastructure/Tokens/JsonWebTokenManager.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Tokens; +using Logitar.Identity.Core.Tokens; using Logitar.Security.Claims; using Microsoft.IdentityModel.Tokens; diff --git a/old/src/Logitar.Identity.Infrastructure/logitar.png b/lib/Logitar.Identity.Infrastructure/logitar.png similarity index 100% rename from old/src/Logitar.Identity.Infrastructure/logitar.png rename to lib/Logitar.Identity.Infrastructure/logitar.png diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs deleted file mode 100644 index 8cc6e12..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/ApiKeyIdConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.ApiKeys; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class ApiKeyIdConverter : JsonConverter -{ - public override ApiKeyId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return ApiKeyId.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, ApiKeyId apiKeyId, JsonSerializerOptions options) - { - writer.WriteStringValue(apiKeyId.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs deleted file mode 100644 index 3f48c19..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/DescriptionConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class DescriptionConverter : JsonConverter -{ - public override DescriptionUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return DescriptionUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, DescriptionUnit description, JsonSerializerOptions options) - { - writer.WriteStringValue(description.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs deleted file mode 100644 index 2a9a3a4..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/DisplayNameConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class DisplayNameConverter : JsonConverter -{ - public override DisplayNameUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return DisplayNameUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, DisplayNameUnit displayName, JsonSerializerOptions options) - { - writer.WriteStringValue(displayName.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs deleted file mode 100644 index 7411884..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/GenderConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Users; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class GenderConverter : JsonConverter -{ - public override GenderUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return GenderUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, GenderUnit gender, JsonSerializerOptions options) - { - writer.WriteStringValue(gender.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs deleted file mode 100644 index 7063dd0..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/LocaleConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class LocaleConverter : JsonConverter -{ - public override LocaleUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return LocaleUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, LocaleUnit locale, JsonSerializerOptions options) - { - writer.WriteStringValue(locale.Code); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs deleted file mode 100644 index af4836d..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/OneTimePasswordIdConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Passwords; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class OneTimePasswordIdConverter : JsonConverter -{ - public override OneTimePasswordId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return OneTimePasswordId.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, OneTimePasswordId oneTimePasswordId, JsonSerializerOptions options) - { - writer.WriteStringValue(oneTimePasswordId.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs deleted file mode 100644 index 90cec39..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/PersonNameConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Users; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class PersonNameConverter : JsonConverter -{ - public override PersonNameUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return PersonNameUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, PersonNameUnit personName, JsonSerializerOptions options) - { - writer.WriteStringValue(personName.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs deleted file mode 100644 index 295762a..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/RoleIdConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Roles; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class RoleIdConverter : JsonConverter -{ - public override RoleId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return RoleId.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, RoleId roleId, JsonSerializerOptions options) - { - writer.WriteStringValue(roleId.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs deleted file mode 100644 index a30d780..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/SessionIdConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Sessions; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class SessionIdConverter : JsonConverter -{ - public override SessionId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return SessionId.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, SessionId sessionId, JsonSerializerOptions options) - { - writer.WriteStringValue(sessionId.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs deleted file mode 100644 index 1c05745..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/TimeZoneConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class TimeZoneConverter : JsonConverter -{ - public override TimeZoneUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return TimeZoneUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, TimeZoneUnit timeZone, JsonSerializerOptions options) - { - writer.WriteStringValue(timeZone.Id); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs deleted file mode 100644 index 44ace47..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/UniqueNameConverter.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class UniqueNameConverter : JsonConverter -{ - private readonly UniqueNameSettings _uniqueNameSettings = new() - { - AllowedCharacters = null // NOTE(fpion): strict validation is not required when deserializing an unique name. - }; - - public override UniqueNameUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return UniqueNameUnit.TryCreate(_uniqueNameSettings, reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, UniqueNameUnit uniqueName, JsonSerializerOptions options) - { - writer.WriteStringValue(uniqueName.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs deleted file mode 100644 index 6a6e339..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/UrlConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class UrlConverter : JsonConverter -{ - public override UrlUnit? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return UrlUnit.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, UrlUnit url, JsonSerializerOptions options) - { - writer.WriteStringValue(url.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs b/old/src/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs deleted file mode 100644 index 7a2bd27..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Converters/UserIdConverter.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Logitar.Identity.Domain.Users; - -namespace Logitar.Identity.Infrastructure.Converters; - -public class UserIdConverter : JsonConverter -{ - public override UserId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return UserId.TryCreate(reader.GetString()); - } - - public override void Write(Utf8JsonWriter writer, UserId userId, JsonSerializerOptions options) - { - writer.WriteStringValue(userId.Value); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs b/old/src/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs deleted file mode 100644 index d1ca96a..0000000 --- a/old/src/Logitar.Identity.Infrastructure/DependencyInjectionExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Logitar.EventSourcing.Infrastructure; -using Logitar.Identity.Domain; -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Tokens; -using Logitar.Identity.Infrastructure.Converters; -using Logitar.Identity.Infrastructure.Passwords; -using Logitar.Identity.Infrastructure.Passwords.Pbkdf2; -using Logitar.Identity.Infrastructure.Tokens; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; - -namespace Logitar.Identity.Infrastructure; - -public static class DependencyInjectionExtensions -{ - public static IServiceCollection AddLogitarIdentityInfrastructure(this IServiceCollection services) - { - return services - .AddLogitarEventSourcingInfrastructure() - .AddLogitarIdentityDomain() - .AddPasswordStrategies() - .AddSingleton(serviceProvider => new EventSerializer(serviceProvider.GetLogitarIdentityJsonConverters())) - .AddSingleton() - .AddSingleton() - .AddSingleton(serviceProvider => - { - IConfiguration configuration = serviceProvider.GetRequiredService(); - return configuration.GetSection("Pbkdf2").Get() ?? new(); - }) - .AddTransient() - .AddTransient(); - } - - public static IEnumerable GetLogitarIdentityJsonConverters(this IServiceProvider serviceProvider) => - [ - serviceProvider.GetRequiredService(), - new ApiKeyIdConverter(), - new DescriptionConverter(), - new DisplayNameConverter(), - new GenderConverter(), - new LocaleConverter(), - new OneTimePasswordIdConverter(), - new PersonNameConverter(), - new RoleIdConverter(), - new SessionIdConverter(), - new TenantIdConverter(), - new TimeZoneConverter(), - new UniqueNameConverter(), - new UrlConverter(), - new UserIdConverter() - ]; - - private static IServiceCollection AddPasswordStrategies(this IServiceCollection services) - { - return services.AddSingleton(); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/EventBus.cs b/old/src/Logitar.Identity.Infrastructure/EventBus.cs deleted file mode 100644 index 5aef56a..0000000 --- a/old/src/Logitar.Identity.Infrastructure/EventBus.cs +++ /dev/null @@ -1,165 +0,0 @@ -using Logitar.EventSourcing; -using Logitar.EventSourcing.Infrastructure; -using Logitar.Identity.Domain.ApiKeys.Events; -using Logitar.Identity.Domain.Passwords.Events; -using Logitar.Identity.Domain.Roles.Events; -using Logitar.Identity.Domain.Sessions.Events; -using Logitar.Identity.Domain.Users.Events; -using Logitar.Identity.Infrastructure.Handlers; -using MediatR; - -namespace Logitar.Identity.Infrastructure; - -public class EventBus : IEventBus -{ - public EventBus(IPublisher publisher, IApiKeyEventHandler apiKeyEventHandler, IOneTimePasswordEventHandler oneTimePasswordEventHandler, - IRoleEventHandler roleEventHandler, ISessionEventHandler sessionEventHandler, IUserEventHandler userEventHandler) - { - Publisher = publisher; - ApiKeyEventHandler = apiKeyEventHandler; - OneTimePasswordEventHandler = oneTimePasswordEventHandler; - RoleEventHandler = roleEventHandler; - SessionEventHandler = sessionEventHandler; - UserEventHandler = userEventHandler; - } - - protected IPublisher Publisher { get; } - protected IApiKeyEventHandler ApiKeyEventHandler { get; } - protected IOneTimePasswordEventHandler OneTimePasswordEventHandler { get; } - protected IRoleEventHandler RoleEventHandler { get; } - protected ISessionEventHandler SessionEventHandler { get; } - protected IUserEventHandler UserEventHandler { get; } - - public virtual async Task PublishAsync(DomainEvent @event, CancellationToken cancellationToken) - { - switch (@event) - { - #region ApiKeys - case ApiKeyAuthenticatedEvent apiKeyAuthenticated: - await ApiKeyEventHandler.HandleAsync(apiKeyAuthenticated, cancellationToken); - break; - case ApiKeyCreatedEvent apiKeyCreated: - await ApiKeyEventHandler.HandleAsync(apiKeyCreated, cancellationToken); - break; - case ApiKeyDeletedEvent apiKeyDeleted: - await ApiKeyEventHandler.HandleAsync(apiKeyDeleted, cancellationToken); - break; - case ApiKeyRoleAddedEvent apiKeyRoleAdded: - await ApiKeyEventHandler.HandleAsync(apiKeyRoleAdded, cancellationToken); - break; - case ApiKeyRoleRemovedEvent apiKeyRoleRemoved: - await ApiKeyEventHandler.HandleAsync(apiKeyRoleRemoved, cancellationToken); - break; - case ApiKeyUpdatedEvent apiKeyUpdated: - await ApiKeyEventHandler.HandleAsync(apiKeyUpdated, cancellationToken); - break; - #endregion - #region OneTimePasswords - case OneTimePasswordCreatedEvent oneTimePasswordCreated: - await OneTimePasswordEventHandler.HandleAsync(oneTimePasswordCreated, cancellationToken); - break; - case OneTimePasswordDeletedEvent oneTimePasswordDeleted: - await OneTimePasswordEventHandler.HandleAsync(oneTimePasswordDeleted, cancellationToken); - break; - case OneTimePasswordUpdatedEvent oneTimePasswordUpdated: - await OneTimePasswordEventHandler.HandleAsync(oneTimePasswordUpdated, cancellationToken); - break; - case OneTimePasswordValidationFailedEvent oneTimePasswordValidationFailed: - await OneTimePasswordEventHandler.HandleAsync(oneTimePasswordValidationFailed, cancellationToken); - break; - case OneTimePasswordValidationSucceededEvent oneTimePasswordValidationSucceeded: - await OneTimePasswordEventHandler.HandleAsync(oneTimePasswordValidationSucceeded, cancellationToken); - break; - #endregion - #region Roles - case RoleCreatedEvent roleCreated: - await RoleEventHandler.HandleAsync(roleCreated, cancellationToken); - break; - case RoleDeletedEvent roleDeleted: - await RoleEventHandler.HandleAsync(roleDeleted, cancellationToken); - break; - case RoleUniqueNameChangedEvent roleUniqueNameChanged: - await RoleEventHandler.HandleAsync(roleUniqueNameChanged, cancellationToken); - break; - case RoleUpdatedEvent roleUpdated: - await RoleEventHandler.HandleAsync(roleUpdated, cancellationToken); - break; - #endregion - #region Sessions - case SessionCreatedEvent sessionCreated: - await SessionEventHandler.HandleAsync(sessionCreated, cancellationToken); - break; - case SessionDeletedEvent sessionDeleted: - await SessionEventHandler.HandleAsync(sessionDeleted, cancellationToken); - break; - case SessionRenewedEvent sessionRenewed: - await SessionEventHandler.HandleAsync(sessionRenewed, cancellationToken); - break; - case SessionSignedOutEvent sessionSignedOut: - await SessionEventHandler.HandleAsync(sessionSignedOut, cancellationToken); - break; - case SessionUpdatedEvent sessionUpdated: - await SessionEventHandler.HandleAsync(sessionUpdated, cancellationToken); - break; - #endregion - #region Users - case UserAddressChangedEvent userAddressChanged: - await UserEventHandler.HandleAsync(userAddressChanged, cancellationToken); - break; - case UserAuthenticatedEvent userAuthenticated: - await UserEventHandler.HandleAsync(userAuthenticated, cancellationToken); - break; - case UserCreatedEvent userCreated: - await UserEventHandler.HandleAsync(userCreated, cancellationToken); - break; - case UserDeletedEvent userDeleted: - await UserEventHandler.HandleAsync(userDeleted, cancellationToken); - break; - case UserDisabledEvent userDisabled: - await UserEventHandler.HandleAsync(userDisabled, cancellationToken); - break; - case UserEmailChangedEvent userEmailChanged: - await UserEventHandler.HandleAsync(userEmailChanged, cancellationToken); - break; - case UserEnabledEvent userEnabled: - await UserEventHandler.HandleAsync(userEnabled, cancellationToken); - break; - case UserIdentifierChangedEvent userIdentifierChanged: - await UserEventHandler.HandleAsync(userIdentifierChanged, cancellationToken); - break; - case UserIdentifierRemovedEvent userIdentifierRemoved: - await UserEventHandler.HandleAsync(userIdentifierRemoved, cancellationToken); - break; - case UserPasswordChangedEvent userPasswordChanged: - await UserEventHandler.HandleAsync(userPasswordChanged, cancellationToken); - break; - case UserPasswordResetEvent userPasswordReset: - await UserEventHandler.HandleAsync(userPasswordReset, cancellationToken); - break; - case UserPasswordUpdatedEvent userPasswordUpdated: - await UserEventHandler.HandleAsync(userPasswordUpdated, cancellationToken); - break; - case UserPhoneChangedEvent userPhoneChanged: - await UserEventHandler.HandleAsync(userPhoneChanged, cancellationToken); - break; - case UserRoleAddedEvent userRoleAdded: - await UserEventHandler.HandleAsync(userRoleAdded, cancellationToken); - break; - case UserRoleRemovedEvent userRoleRemoved: - await UserEventHandler.HandleAsync(userRoleRemoved, cancellationToken); - break; - case UserSignedInEvent userSignedIn: - await UserEventHandler.HandleAsync(userSignedIn, cancellationToken); - break; - case UserUniqueNameChangedEvent userUniqueNameChanged: - await UserEventHandler.HandleAsync(userUniqueNameChanged, cancellationToken); - break; - case UserUpdatedEvent userUpdated: - await UserEventHandler.HandleAsync(userUpdated, cancellationToken); - break; - #endregion - } - - await Publisher.Publish(@event, cancellationToken); - } -} diff --git a/old/src/Logitar.Identity.Infrastructure/Handlers/IApiKeyEventHandler.cs b/old/src/Logitar.Identity.Infrastructure/Handlers/IApiKeyEventHandler.cs deleted file mode 100644 index 8f1b620..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Handlers/IApiKeyEventHandler.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Logitar.Identity.Domain.ApiKeys.Events; - -namespace Logitar.Identity.Infrastructure.Handlers; - -public interface IApiKeyEventHandler -{ - Task HandleAsync(ApiKeyAuthenticatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(ApiKeyCreatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(ApiKeyDeletedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(ApiKeyRoleAddedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(ApiKeyRoleRemovedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(ApiKeyUpdatedEvent @event, CancellationToken cancellationToken = default); -} diff --git a/old/src/Logitar.Identity.Infrastructure/Handlers/IOneTimePasswordEventHandler.cs b/old/src/Logitar.Identity.Infrastructure/Handlers/IOneTimePasswordEventHandler.cs deleted file mode 100644 index aac71e6..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Handlers/IOneTimePasswordEventHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Logitar.Identity.Domain.Passwords.Events; - -namespace Logitar.Identity.Infrastructure.Handlers; - -public interface IOneTimePasswordEventHandler -{ - Task HandleAsync(OneTimePasswordCreatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(OneTimePasswordDeletedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(OneTimePasswordUpdatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(OneTimePasswordValidationFailedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(OneTimePasswordValidationSucceededEvent @event, CancellationToken cancellationToken = default); -} diff --git a/old/src/Logitar.Identity.Infrastructure/Handlers/IRoleEventHandler.cs b/old/src/Logitar.Identity.Infrastructure/Handlers/IRoleEventHandler.cs deleted file mode 100644 index dec3deb..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Handlers/IRoleEventHandler.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Logitar.Identity.Domain.Roles.Events; - -namespace Logitar.Identity.Infrastructure.Handlers; - -public interface IRoleEventHandler -{ - Task HandleAsync(RoleCreatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(RoleDeletedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(RoleUniqueNameChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(RoleUpdatedEvent @event, CancellationToken cancellationToken = default); -} diff --git a/old/src/Logitar.Identity.Infrastructure/Handlers/ISessionEventHandler.cs b/old/src/Logitar.Identity.Infrastructure/Handlers/ISessionEventHandler.cs deleted file mode 100644 index 37c7aca..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Handlers/ISessionEventHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Logitar.Identity.Domain.Sessions.Events; - -namespace Logitar.Identity.Infrastructure.Handlers; - -public interface ISessionEventHandler -{ - Task HandleAsync(SessionCreatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(SessionDeletedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(SessionRenewedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(SessionSignedOutEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(SessionUpdatedEvent @event, CancellationToken cancellationToken = default); -} diff --git a/old/src/Logitar.Identity.Infrastructure/Handlers/IUserEventHandler.cs b/old/src/Logitar.Identity.Infrastructure/Handlers/IUserEventHandler.cs deleted file mode 100644 index 5ecb713..0000000 --- a/old/src/Logitar.Identity.Infrastructure/Handlers/IUserEventHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Logitar.Identity.Domain.Users.Events; - -namespace Logitar.Identity.Infrastructure.Handlers; - -public interface IUserEventHandler -{ - Task HandleAsync(UserAddressChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserAuthenticatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserCreatedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserDeletedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserDisabledEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserEnabledEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserEmailChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserIdentifierChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserIdentifierRemovedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserPasswordEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserPhoneChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserRoleAddedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserRoleRemovedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserSignedInEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserUniqueNameChangedEvent @event, CancellationToken cancellationToken = default); - Task HandleAsync(UserUpdatedEvent @event, CancellationToken cancellationToken = default); -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyAggregateTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyAggregateTests.cs deleted file mode 100644 index 8f19d42..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyAggregateTests.cs +++ /dev/null @@ -1,351 +0,0 @@ -using Bogus; -using FluentValidation.Results; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.ApiKeys.Events; -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Roles; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.ApiKeys; - -[Trait(Traits.Category, Categories.Unit)] -public class ApiKeyAggregateTests -{ - private const string SecretString = "Test123!"; - - private readonly Faker _faker = new(); - private readonly UniqueNameSettings _uniqueNameSettings = new(); - private readonly RoleAggregate _role; - private readonly ApiKeyAggregate _apiKey; - - public ApiKeyAggregateTests() - { - _role = new(new UniqueNameUnit(_uniqueNameSettings, "admin")); - _apiKey = new(new DisplayNameUnit("Default"), new Base64Password(SecretString)); - } - - [Fact(DisplayName = "AddRole: it should add the role to the API key when it does not have the role.")] - public void AddRole_it_should_add_the_role_to_the_Api_key_when_it_does_not_have_the_role() - { - Assert.False(_apiKey.HasRole(_role)); - Assert.Empty(_apiKey.Roles); - - _apiKey.AddRole(_role); - Assert.Contains(_apiKey.Changes, changes => changes is ApiKeyRoleAddedEvent @event && @event.RoleId == _role.Id); - Assert.True(_apiKey.HasRole(_role)); - Assert.Single(_apiKey.Roles, _role.Id); - - _apiKey.ClearChanges(); - _apiKey.AddRole(_role); - Assert.False(_apiKey.HasChanges); - } - - [Fact(DisplayName = "AddRole: it should throw TenantMismatchException when the role is in a different tenant.")] - public void AddRole_it_should_throw_TenantMismatchException_when_the_role_is_in_a_different_tenant() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - RoleAggregate role = new(_role.UniqueName, tenantId); - - var exception = Assert.Throws(() => _apiKey.AddRole(role)); - Assert.Equal(_apiKey.TenantId, exception.ExpectedTenantId); - Assert.Equal(role.TenantId, exception.ActualTenantId); - } - - [Fact(DisplayName = "Authenticate: it should authenticate the API key.")] - public void Authenticate_it_should_authenticate_the_Api_key() - { - _apiKey.Authenticate(SecretString); - - ApiKeyAuthenticatedEvent @event = (ApiKeyAuthenticatedEvent)Assert.Single(_apiKey.Changes, change => change is ApiKeyAuthenticatedEvent); - Assert.Equal(_apiKey.Id.Value, @event.ActorId.Value); - Assert.Equal(@event.OccurredOn, _apiKey.AuthenticatedOn); - } - - [Fact(DisplayName = "Authenticate: it should authenticate the API key using the specified actor identifier.")] - public void Authenticate_it_should_authenticate_the_Api_key_using_the_specified_actor_identifier() - { - ActorId actorId = ActorId.NewId(); - _apiKey.Authenticate(SecretString, actorId); - Assert.Contains(_apiKey.Changes, change => change is ApiKeyAuthenticatedEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "Authenticate: it should throw ApiKeyIsExpiredException when the API key is expired.")] - public void Authenticate_it_should_throw_ApiKeyIsExpiredException_when_the_Api_key_is_expired() - { - _apiKey.SetExpiration(DateTime.Now.AddMilliseconds(100)); - - Thread.Sleep(TimeSpan.FromMilliseconds(100)); - - var exception = Assert.Throws(() => _apiKey.Authenticate(SecretString)); - Assert.Equal(_apiKey.Id, exception.ApiKeyId); - } - - [Fact(DisplayName = "Authenticate: it should throw IncorrectApiKeySecretException when the attempted secret is incorrect.")] - public void Authenticate_it_should_throw_IncorrectApiKeySecretException_when_the_attempted_secret_is_incorrect() - { - string secret = SecretString[1..]; - - var exception = Assert.Throws(() => _apiKey.Authenticate(secret)); - Assert.Equal(secret, exception.AttemptedSecret); - Assert.Equal(_apiKey.Id, exception.ApiKeyId); - } - - [Fact(DisplayName = "Authenticate: it should throw IncorrectApiKeySecretException when the API key has no secret.")] - public void Authenticate_it_should_throw_IncorrectApiKeySecretException_when_the_Api_key_has_no_secret() - { - ApiKeyAggregate apiKey = new(); - - var exception = Assert.Throws(() => apiKey.Authenticate(SecretString)); - Assert.Equal(SecretString, exception.AttemptedSecret); - Assert.Equal(apiKey.Id, exception.ApiKeyId); - } - - [Fact(DisplayName = "ctor: it should create a new API key with parameters.")] - public void ctor_it_should_create_a_new_Api_key_with_parameters() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - ActorId actorId = ActorId.NewId(); - ApiKeyId id = new(Guid.NewGuid().ToString()); - Base64Password secret = new(SecretString); - - ApiKeyAggregate apiKey = new(_apiKey.DisplayName, secret, tenantId, actorId, id); - - Assert.Equal(id, apiKey.Id); - Assert.Equal(actorId, apiKey.CreatedBy); - Assert.Equal(tenantId, apiKey.TenantId); - Assert.Equal(_apiKey.DisplayName, apiKey.DisplayName); - AssertSecret(apiKey, SecretString); - } - - [Fact(DisplayName = "Delete: it should delete the API key when it is not deleted.")] - public void Delete_it_should_delete_the_Api_key_when_it_is_not_deleted() - { - Assert.False(_apiKey.IsDeleted); - - _apiKey.Delete(); - Assert.True(_apiKey.IsDeleted); - Assert.Contains(_apiKey.Changes, change => change is ApiKeyDeletedEvent); - - _apiKey.ClearChanges(); - _apiKey.Delete(); - Assert.False(_apiKey.HasChanges); - } - - [Fact(DisplayName = "Description: it should change the description when it is different.")] - public void Description_it_should_change_the_description_when_it_is_different() - { - DescriptionUnit description = new("This is the default API key."); - _apiKey.Description = description; - Assert.Equal(description, _apiKey.Description); - - _apiKey.Update(); - - _apiKey.Description = description; - AssertHasNoUpdate(_apiKey); - } - - [Fact(DisplayName = "DisplayName: it should change the display name when it is different.")] - public void DisplayName_it_should_change_the_display_name_when_it_is_different() - { - DisplayNameUnit displayName = new("[LEGACY] Default"); - _apiKey.DisplayName = displayName; - Assert.Equal(displayName, _apiKey.DisplayName); - - _apiKey.Update(); - - _apiKey.DisplayName = displayName; - AssertHasNoUpdate(_apiKey); - } - - [Fact(DisplayName = "HasRole: it should return false when the API key does not have the specified role.")] - public void HasRole_it_should_return_false_when_the_Api_key_does_not_have_the_specified_role() - { - Assert.False(_apiKey.HasRole(_role)); - } - - [Fact(DisplayName = "HasRole: it should return true when the API key does have the specified role.")] - public void HasRole_it_should_return_true_when_the_Api_key_does_have_the_specified_role() - { - _apiKey.AddRole(_role); - Assert.True(_apiKey.HasRole(_role)); - } - - [Fact(DisplayName = "IsExpired: it should return false when the API key has no expiration.")] - public void IsExpired_it_should_return_false_when_the_Api_key_has_no_expiration() - { - Assert.Null(_apiKey.ExpiresOn); - Assert.False(_apiKey.IsExpired()); - } - - [Fact(DisplayName = "IsExpired: it should return false when the API key is not expired.")] - public void IsExpired_it_should_return_false_when_the_Api_key_is_not_expired() - { - _apiKey.SetExpiration(DateTime.Now.AddYears(1)); - - Assert.False(_apiKey.IsExpired()); - } - - [Fact(DisplayName = "IsExpired: it should return true when the API key is expired.")] - public void IsExpired_it_should_return_true_when_the_Api_key_is_expired() - { - _apiKey.SetExpiration(DateTime.Now.AddMilliseconds(100)); - - Thread.Sleep(TimeSpan.FromMilliseconds(100)); - - Assert.True(_apiKey.IsExpired()); - } - - [Fact(DisplayName = "RemoveCustomAttribute: it should remove an existing custom attribute.")] - public void RemoveCustomAttribute_it_should_remove_an_existing_custom_attribute() - { - string key = "remove_users"; - - _apiKey.SetCustomAttribute(key, bool.TrueString); - _apiKey.Update(); - - _apiKey.RemoveCustomAttribute($" {key} "); - _apiKey.Update(); - Assert.False(_apiKey.CustomAttributes.ContainsKey(key)); - - _apiKey.RemoveCustomAttribute(key); - AssertHasNoUpdate(_apiKey); - } - - [Fact(DisplayName = "RemoveRole: it should remove the role from the API key when it has the role.")] - public void RemoveRole_it_should_remove_the_role_from_the_Api_key_when_it_has_the_role() - { - _apiKey.AddRole(_role); - Assert.True(_apiKey.HasRole(_role)); - Assert.Single(_apiKey.Roles, _role.Id); - _apiKey.ClearChanges(); - - _apiKey.RemoveRole(_role); - Assert.Contains(_apiKey.Changes, changes => changes is ApiKeyRoleRemovedEvent @event && @event.RoleId == _role.Id); - Assert.False(_apiKey.HasRole(_role)); - Assert.Empty(_apiKey.Roles); - - _apiKey.ClearChanges(); - _apiKey.RemoveRole(_role); - Assert.False(_apiKey.HasChanges); - } - - [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute when it is different.")] - public void SetCustomAttribute_it_should_set_a_custom_attribute_when_it_is_different() - { - string key = " remove_users "; - string value = $" {bool.TrueString} "; - _apiKey.SetCustomAttribute(key, value); - Assert.Equal(value.Trim(), _apiKey.CustomAttributes[key.Trim()]); - - _apiKey.Update(); - - _apiKey.SetCustomAttribute(key, value); - AssertHasNoUpdate(_apiKey); - } - - [Fact(DisplayName = "SetCustomAttribute: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomAttribute_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _apiKey.SetCustomAttribute(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _apiKey.SetCustomAttribute(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _apiKey.SetCustomAttribute("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _apiKey.SetCustomAttribute("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "SetExpiration: it should change the expiration when it is different.")] - public void SetExpiration_it_should_change_the_expiration_when_it_is_different() - { - DateTime expiresOn = DateTime.Now.AddYears(1); - - _apiKey.SetExpiration(expiresOn); - Assert.Equal(expiresOn, _apiKey.ExpiresOn); - - _apiKey.Update(); - - _apiKey.SetExpiration(expiresOn); - AssertHasNoUpdate(_apiKey); - } - - [Fact(DisplayName = "SetExpiration: it should throw ValidationException when the expiration is not in the future.")] - public void SetExpiration_it_should_throw_ValidationException_when_the_expiration_is_not_in_the_future() - { - DateTime expiresOn = DateTime.Now.AddDays(-1); - string propertyName = "ExpiresOn"; - - var exception = Assert.Throws(() => _apiKey.SetExpiration(expiresOn, propertyName)); - ValidationFailure failure = Assert.Single(exception.Errors); - Assert.Equal(expiresOn, failure.AttemptedValue); - Assert.Equal("FutureValidator", failure.ErrorCode); - Assert.Equal(propertyName, failure.PropertyName); - } - - [Fact(DisplayName = "SetExpiration: it should throw ValidationException when the expiration is postponed.")] - public void SetExpiration_it_should_throw_ValidationException_when_the_expiration_is_postponed() - { - _apiKey.SetExpiration(DateTime.Now.AddMonths(6)); - - DateTime expiresOn = DateTime.Now.AddYears(1); - string propertyName = "ExpiresOn"; - - var exception = Assert.Throws(() => _apiKey.SetExpiration(expiresOn, propertyName)); - ValidationFailure failure = Assert.Single(exception.Errors); - Assert.Equal(expiresOn, failure.AttemptedValue); - Assert.Equal("LessThanOrEqualValidator", failure.ErrorCode); - Assert.Equal(propertyName, failure.PropertyName); - } - - [Fact(DisplayName = "ToString: it should return the correct string representation.")] - public void ToString_it_should_return_the_correct_string_representation() - { - Assert.StartsWith($"{_apiKey.DisplayName.Value} | ", _apiKey.ToString()); - } - - [Fact(DisplayName = "Update: it should update the API key when it has changes.")] - public void Update_it_should_update_the_Api_key_when_it_has_changes() - { - ActorId actorId = ActorId.NewId(); - - _apiKey.Description = new DescriptionUnit("This is the default API key."); - _apiKey.Update(actorId); - Assert.Equal(actorId, _apiKey.UpdatedBy); - - long version = _apiKey.Version; - _apiKey.Update(actorId); - Assert.Equal(version, _apiKey.Version); - } - - private static void AssertHasNoUpdate(ApiKeyAggregate apiKey) - { - FieldInfo? field = apiKey.GetType().GetField("_updatedEvent", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - ApiKeyUpdatedEvent? updated = field.GetValue(apiKey) as ApiKeyUpdatedEvent; - Assert.NotNull(updated); - Assert.False(updated.HasChanges); - } - private static void AssertSecret(ApiKeyAggregate apiKey, string? secret) - { - FieldInfo? field = apiKey.GetType().GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - Password? instance = field.GetValue(apiKey) as Password; - if (secret == null) - { - Assert.Null(instance); - } - else - { - Assert.NotNull(instance); - Assert.True(instance.IsMatch(secret)); - } - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyIdTests.cs deleted file mode 100644 index 9cbca41..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/ApiKeyIdTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; - -namespace Logitar.Identity.Domain.ApiKeys; - -[Trait(Traits.Category, Categories.Unit)] -public class ApiKeyIdTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "ctor: it should create an identifier from a Guid.")] - public void ctor_it_should_create_an_identifier_from_a_Guid() - { - Guid guid = Guid.NewGuid(); - ApiKeyId id = new(guid); - string expected = new AggregateId(guid).Value; - Assert.Equal(expected, id.Value); - } - - [Theory(DisplayName = "ctor: it should create a new API key identifier.")] - [InlineData("64192609-6ad1-4f54-a8f0-a44372f229c8")] - [InlineData(" bbea05b5-75c0-4ab5-8572-6d0dee8aa046 ")] - public void ctor_it_should_create_a_new_Api_key_identifier(string value) - { - ApiKeyId apiKeyId = new(value); - Assert.Equal(value.Trim(), apiKeyId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(ApiKeyId); - - var exception = Assert.Throws(() => new ApiKeyId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(ApiKeyId); - - var exception = Assert.Throws(() => new ApiKeyId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "NewId: it should create a new API key ID.")] - public void NewId_it_should_create_a_new_Api_key_Id() - { - ApiKeyId id = ApiKeyId.NewId(); - Assert.Equal(id.AggregateId.Value, id.Value); - } - - [Fact(DisplayName = "ToGuid: it should convert the identifier to a Guid.")] - public void ToGuid_it_should_convert_the_identifier_to_a_Guid() - { - Guid guid = Guid.NewGuid(); - ApiKeyId id = new(new AggregateId(guid)); - Assert.Equal(guid, id.ToGuid()); - } - - [Theory(DisplayName = "TryCreate: it should return an API key identifier when the value is not empty.")] - [InlineData("268f59a5-eaa4-4b04-9d96-d1bbaa453a19")] - [InlineData(" 0aee1e54-3f7d-4b87-a82e-d2e7924c46bc ")] - public void TryCreate_it_should_return_an_Api_key_identifier_when_the_value_is_not_empty(string value) - { - ApiKeyId? apiKeyId = ApiKeyId.TryCreate(value); - Assert.NotNull(apiKeyId); - Assert.Equal(value.Trim(), apiKeyId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(ApiKeyId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/Events/ApiKeyUpdatedEventTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/Events/ApiKeyUpdatedEventTests.cs deleted file mode 100644 index 7f18074..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/ApiKeys/Events/ApiKeyUpdatedEventTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.ApiKeys.Events; - -[Trait(Traits.Category, Categories.Unit)] -public class ApiKeyUpdatedEventTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "It should be serializable and deserializable.")] - public void It_should_be_serializable_and_deserializable() - { - ApiKeyUpdatedEvent @event = new(); - @event.CustomAttributes.Add("Owner", _faker.Person.UserName); - @event.CustomAttributes.Add("SubSystem", "Identity"); - - string json = JsonSerializer.Serialize(@event); - Assert.DoesNotContain("haschanges", json.ToLower()); - - ApiKeyUpdatedEvent? deserialized = JsonSerializer.Deserialize(json); - Assert.NotNull(deserialized); - Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/Events/OneTimePasswordUpdatedEventTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/Events/OneTimePasswordUpdatedEventTests.cs deleted file mode 100644 index 8f0e61b..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/Events/OneTimePasswordUpdatedEventTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Logitar.Identity.Domain.Passwords.Events; - -[Trait(Traits.Category, Categories.Unit)] -public class OneTimePasswordUpdatedEventTests -{ - [Fact(DisplayName = "It should be serializable and deserializable.")] - public void It_should_be_serializable_and_deserializable() - { - OneTimePasswordUpdatedEvent @event = new(); - @event.CustomAttributes.Add("Purpose", "reset_password"); - @event.CustomAttributes.Add("UserId", Guid.NewGuid().ToString()); - - string json = JsonSerializer.Serialize(@event); - Assert.DoesNotContain("haschanges", json.ToLower()); - - OneTimePasswordUpdatedEvent? deserialized = JsonSerializer.Deserialize(json); - Assert.NotNull(deserialized); - Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordAggregateTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordAggregateTests.cs deleted file mode 100644 index 5329b59..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordAggregateTests.cs +++ /dev/null @@ -1,254 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Passwords.Events; -using Logitar.Identity.Domain.Passwords.Validators; -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Passwords; - -[Trait(Traits.Category, Categories.Unit)] -public class OneTimePasswordAggregateTests -{ - private const string PasswordString = "420742"; - - private readonly Faker _faker = new(); - - private readonly Base64Password _password = new(PasswordString); - private readonly OneTimePasswordAggregate _oneTimePassword; - - public OneTimePasswordAggregateTests() - { - _oneTimePassword = new(_password); - } - - [Fact(DisplayName = "ctor: it should create a new One-Time Password with parameters.")] - public void ctor_it_should_create_a_new_One_Time_Password_with_parameters() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - ActorId actorId = ActorId.NewId(); - DateTime expiresOn = DateTime.Now.AddHours(1); - int maximumAttempts = 5; - OneTimePasswordId id = new(Guid.NewGuid().ToString()); - - OneTimePasswordAggregate oneTimePassword = new(_password, tenantId, expiresOn, maximumAttempts, actorId, id); - AssertPassword(oneTimePassword, PasswordString); - - Assert.Equal(id, oneTimePassword.Id); - Assert.Equal(actorId, oneTimePassword.CreatedBy); - Assert.Equal(expiresOn, oneTimePassword.ExpiresOn); - Assert.Equal(maximumAttempts, oneTimePassword.MaximumAttempts); - Assert.Equal(tenantId, oneTimePassword.TenantId); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the expiration is not in the future.")] - public void ctor_it_should_throw_ValidationException_when_the_expiration_is_not_in_the_future() - { - var exception = Assert.Throws(() => new OneTimePasswordAggregate(_password, expiresOn: DateTime.Now.AddMinutes(-1))); - Assert.Contains(exception.Errors, e => e.ErrorCode == nameof(FutureValidator)); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the maximum attempts are lesser or equal to 0.")] - public void ctor_it_should_throw_ValidationException_when_the_maximum_attempts_are_lesser_or_equal_to_0() - { - FluentValidation.ValidationException exception; - - exception = Assert.Throws(() => new OneTimePasswordAggregate(_password, maximumAttempts: 0)); - Assert.Contains(exception.Errors, e => e.ErrorCode == nameof(MaximumAttemptsValidator)); - - exception = Assert.Throws(() => new OneTimePasswordAggregate(_password, maximumAttempts: -1)); - Assert.Contains(exception.Errors, e => e.ErrorCode == nameof(MaximumAttemptsValidator)); - } - - [Fact(DisplayName = "Delete: it should delete the One-Time Password when it is not deleted.")] - public void Delete_it_should_delete_the_One_Time_Password_when_it_is_not_deleted() - { - Assert.False(_oneTimePassword.IsDeleted); - - _oneTimePassword.Delete(); - Assert.True(_oneTimePassword.IsDeleted); - Assert.Contains(_oneTimePassword.Changes, change => change is OneTimePasswordDeletedEvent); - - _oneTimePassword.ClearChanges(); - _oneTimePassword.Delete(); - Assert.False(_oneTimePassword.HasChanges); - } - - [Fact(DisplayName = "IsExpired: it should return false when the One-Time Password has no expiration.")] - public void IsExpired_it_should_return_false_when_the_One_Time_Password_has_no_expiration() - { - Assert.Null(_oneTimePassword.ExpiresOn); - Assert.False(_oneTimePassword.IsExpired()); - } - - [Fact(DisplayName = "IsExpired: it should return false when the One-Time Password is not expired.")] - public void IsExpired_it_should_return_false_when_the_One_Time_Password_is_not_expired() - { - DateTime now = DateTime.Now; - OneTimePasswordAggregate oneTimePassword = new(_password, expiresOn: now.AddHours(1)); - - Assert.False(oneTimePassword.IsExpired(now.AddMinutes(50))); - } - - [Fact(DisplayName = "IsExpired: it should return true when the One-Time Password is expired.")] - public void IsExpired_it_should_return_true_when_the_One_Time_Password_is_expired() - { - DateTime now = DateTime.Now; - OneTimePasswordAggregate oneTimePassword = new(_password, expiresOn: now.AddHours(1)); - - Assert.True(oneTimePassword.IsExpired(now.AddMinutes(70))); - } - - [Fact(DisplayName = "RemoveCustomAttribute: it should remove an existing custom attribute.")] - public void RemoveCustomAttribute_it_should_remove_an_existing_custom_attribute() - { - string key = "Purpose"; - - _oneTimePassword.SetCustomAttribute(key, "MFA"); - _oneTimePassword.Update(); - - _oneTimePassword.RemoveCustomAttribute($" {key} "); - _oneTimePassword.Update(); - Assert.False(_oneTimePassword.CustomAttributes.ContainsKey(key)); - - _oneTimePassword.RemoveCustomAttribute(key); - AssertHasNoUpdate(_oneTimePassword); - } - - [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute when it is different.")] - public void SetCustomAttribute_it_should_set_a_custom_attribute_when_it_is_different() - { - string key = " Purpose "; - string value = " MFA "; - _oneTimePassword.SetCustomAttribute(key, value); - Assert.Equal(value.Trim(), _oneTimePassword.CustomAttributes[key.Trim()]); - - _oneTimePassword.Update(); - - _oneTimePassword.SetCustomAttribute(key, value); - AssertHasNoUpdate(_oneTimePassword); - } - - [Fact(DisplayName = "SetCustomAttribute: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomAttribute_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _oneTimePassword.SetCustomAttribute(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _oneTimePassword.SetCustomAttribute(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _oneTimePassword.SetCustomAttribute("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _oneTimePassword.SetCustomAttribute("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "Update: it should update the One-Time Password when it has changes.")] - public void Update_it_should_update_the_One_Time_Password_when_it_has_changes() - { - ActorId actorId = ActorId.NewId(); - - _oneTimePassword.SetCustomAttribute("Purpose", "MFA"); - _oneTimePassword.Update(actorId); - Assert.Equal(actorId, _oneTimePassword.UpdatedBy); - - long version = _oneTimePassword.Version; - _oneTimePassword.Update(actorId); - Assert.Equal(version, _oneTimePassword.Version); - } - - [Fact(DisplayName = "Validate: it should handle validation failure correctly.")] - public void Validate_it_should_handle_validation_failure_correctly() - { - int attemptCount = _oneTimePassword.AttemptCount; - string incorrectPassword = new(PasswordString.Reverse().ToArray()); - ActorId actorId = ActorId.NewId(); - - var exception = Assert.Throws(() => _oneTimePassword.Validate(incorrectPassword, actorId)); - Assert.Equal(_oneTimePassword.Id, exception.OneTimePasswordId); - Assert.Equal(incorrectPassword, exception.AttemptedPassword); - - Assert.Equal(attemptCount + 1, _oneTimePassword.AttemptCount); - - Assert.Contains(_oneTimePassword.Changes, change => change is OneTimePasswordValidationFailedEvent && change.ActorId == actorId); - } - - [Fact(DisplayName = "Validate: it should handle validation success correctly.")] - public void Validate_it_should_handle_validation_success_correctly() - { - int attemptCount = _oneTimePassword.AttemptCount; - ActorId actorId = ActorId.NewId(); - - _oneTimePassword.Validate(PasswordString, actorId); - Assert.Equal(attemptCount + 1, _oneTimePassword.AttemptCount); - Assert.True(_oneTimePassword.HasValidationSucceeded); - - Assert.Contains(_oneTimePassword.Changes, change => change is OneTimePasswordValidationSucceededEvent && change.ActorId == actorId); - } - - [Fact(DisplayName = "Validate: it should throw OneTimePasswordAlreadyUsedException when the One-Time Password has already been used.")] - public void Validate_it_should_throw_OneTimePasswordAlreadyUsedException_when_the_One_Time_Password_has_already_been_used() - { - _oneTimePassword.Validate(PasswordString); - - var exception = Assert.Throws(() => _oneTimePassword.Validate(PasswordString)); - Assert.Equal(_oneTimePassword.Id, exception.OneTimePasswordId); - } - - [Fact(DisplayName = "Validate: it should throw MaximumAttemptsReachedException when the maximum number of attempts has been reached.")] - public void Validate_it_should_throw_MaximumAttemptsReachedException_when_the_maximum_number_of_attempts_has_been_reached() - { - OneTimePasswordAggregate oneTimePassword = new(_password, maximumAttempts: 1); - string incorrectPassword = new(PasswordString.Reverse().ToArray()); - try - { - oneTimePassword.Validate(incorrectPassword); - } - catch (IncorrectOneTimePasswordPasswordException) - { - } - - var exception = Assert.Throws(() => oneTimePassword.Validate(PasswordString)); - Assert.Equal(oneTimePassword.Id, exception.OneTimePasswordId); - Assert.Equal(oneTimePassword.AttemptCount, exception.AttemptCount); - } - - [Fact(DisplayName = "Validate: it should throw OneTimePasswordIsExpiredException when the One-Time Password is expired.")] - public void Validate_it_should_throw_OneTimePasswordIsExpiredException_when_the_One_Time_Password_is_expired() - { - OneTimePasswordAggregate oneTimePassword = new(_password, expiresOn: DateTime.Now.AddMilliseconds(50)); - - Thread.Sleep(100); - - var exception = Assert.Throws(() => oneTimePassword.Validate(PasswordString)); - Assert.Equal(oneTimePassword.Id, exception.OneTimePasswordId); - } - - private static void AssertHasNoUpdate(OneTimePasswordAggregate oneTimePassword) - { - FieldInfo? field = oneTimePassword.GetType().GetField("_updatedEvent", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - OneTimePasswordUpdatedEvent? updated = field.GetValue(oneTimePassword) as OneTimePasswordUpdatedEvent; - Assert.NotNull(updated); - Assert.False(updated.HasChanges); - } - private static void AssertPassword(OneTimePasswordAggregate oneTimePassword, string? password) - { - FieldInfo? field = oneTimePassword.GetType().GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - Password? instance = field.GetValue(oneTimePassword) as Password; - if (password == null) - { - Assert.Null(instance); - } - else - { - Assert.NotNull(instance); - Assert.True(instance.IsMatch(password)); - } - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordIdTests.cs deleted file mode 100644 index b61974a..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/OneTimePasswordIdTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; - -namespace Logitar.Identity.Domain.Passwords; - -[Trait(Traits.Category, Categories.Unit)] -public class OneTimePasswordIdTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "ctor: it should create an identifier from a Guid.")] - public void ctor_it_should_create_an_identifier_from_a_Guid() - { - Guid guid = Guid.NewGuid(); - OneTimePasswordId id = new(guid); - string expected = new AggregateId(guid).Value; - Assert.Equal(expected, id.Value); - } - - [Theory(DisplayName = "ctor: it should create a new One-Time Password identifier.")] - [InlineData("86f2b595-a803-40c5-b270-f33ea53620b0")] - [InlineData(" admin ")] - public void ctor_it_should_create_a_new_One_Time_Password_identifier(string value) - { - OneTimePasswordId oneTimePasswordId = new(value); - Assert.Equal(value.Trim(), oneTimePasswordId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(OneTimePasswordId); - - var exception = Assert.Throws(() => new OneTimePasswordId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(OneTimePasswordId); - - var exception = Assert.Throws(() => new OneTimePasswordId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "NewId: it should create a new One-Time Password ID.")] - public void NewId_it_should_create_a_new_One_Time_Password_Id() - { - OneTimePasswordId id = OneTimePasswordId.NewId(); - Assert.Equal(id.AggregateId.Value, id.Value); - } - - [Fact(DisplayName = "ToGuid: it should convert the identifier to a Guid.")] - public void ToGuid_it_should_convert_the_identifier_to_a_Guid() - { - Guid guid = Guid.NewGuid(); - OneTimePasswordId id = new(new AggregateId(guid)); - Assert.Equal(guid, id.ToGuid()); - } - - [Theory(DisplayName = "TryCreate: it should return a One-Time Password identifier when the value is not empty.")] - [InlineData("85107c8e-0730-4dc1-99f4-4008d0ea7688")] - [InlineData(" admin ")] - public void TryCreate_it_should_return_a_One_Time_Password_when_the_value_is_not_empty(string value) - { - OneTimePasswordId? oneTimePasswordId = OneTimePasswordId.TryCreate(value); - Assert.NotNull(oneTimePasswordId); - Assert.Equal(value.Trim(), oneTimePasswordId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(OneTimePasswordId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/PasswordValidatorTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/PasswordValidatorTests.cs deleted file mode 100644 index 7e43870..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Passwords/PasswordValidatorTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using FluentValidation.Results; -using Logitar.Identity.Domain.Passwords.Validators; -using Logitar.Identity.Domain.Settings; - -namespace Logitar.Identity.Domain.Passwords; - -[Trait(Traits.Category, Categories.Unit)] -public class PasswordValidatorTests -{ - private readonly PasswordSettings _settings = new(); - private readonly PasswordValidator _validator; - - public PasswordValidatorTests() - { - _validator = new(_settings); - } - - [Fact(DisplayName = "Validation should fail when the password does not contain a digit character.")] - public void Validation_should_fail_when_the_password_does_not_contain_a_digit_character() - { - ValidationResult result = _validator.Validate("AAaa!!!!"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordRequiresDigit"); - } - - [Fact(DisplayName = "Validation should fail when the password does not contain a lowercase character.")] - public void Validation_should_fail_when_the_password_does_not_contain_a_lowercase_character() - { - ValidationResult result = _validator.Validate("AAAA!!11"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordRequiresLower"); - } - - [Fact(DisplayName = "Validation should fail when the password does not contain a non-alphanumeric character.")] - public void Validation_should_fail_when_the_password_does_not_contain_a_non_alphanumeric_character() - { - ValidationResult result = _validator.Validate("AAaa1111"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordRequiresNonAlphanumeric"); - } - - [Fact(DisplayName = "Validation should fail when the password does not contain an uppercase character.")] - public void Validation_should_fail_when_the_password_does_not_contain_an_uppercase_character() - { - ValidationResult result = _validator.Validate("aaaa!!11"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordRequiresUpper"); - } - - [Fact(DisplayName = "Validation should fail when the password does not contain enough unique characters.")] - public void Validation_should_fail_when_the_password_does_not_contain_enough_unique_characters() - { - ValidationResult result = _validator.Validate("AAaa!!11"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordRequiresUniqueChars"); - } - - [Fact(DisplayName = "Validation should fail when the password is too short.")] - public void Validation_should_fail_when_the_password_is_too_short() - { - ValidationResult result = _validator.Validate("Aa!1"); - Assert.False(result.IsValid); - Assert.Contains(result.Errors, e => e.ErrorCode == "PasswordTooShort"); - } - - [Fact(DisplayName = "Validation should succeed when criterias are met.")] - public void Validation_should_succeed_when_criterias_are_met() - { - ValidationResult result = _validator.Validate("Test123!"); - Assert.True(result.IsValid); - Assert.Empty(result.Errors); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/Events/RoleUpdatedEventTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Roles/Events/RoleUpdatedEventTests.cs deleted file mode 100644 index 6a6656f..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/Events/RoleUpdatedEventTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Logitar.Identity.Domain.Roles.Events; - -[Trait(Traits.Category, Categories.Unit)] -public class RoleUpdatedEventTests -{ - [Fact(DisplayName = "It should be serializable and deserializable.")] - public void It_should_be_serializable_and_deserializable() - { - RoleUpdatedEvent @event = new(); - @event.CustomAttributes.Add("manage_users", bool.FalseString); - @event.CustomAttributes.Add("configuration", bool.TrueString); - - string json = JsonSerializer.Serialize(@event); - Assert.DoesNotContain("haschanges", json.ToLower()); - - RoleUpdatedEvent? deserialized = JsonSerializer.Deserialize(json); - Assert.NotNull(deserialized); - Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleAggregateTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleAggregateTests.cs deleted file mode 100644 index d7ac8ea..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleAggregateTests.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Roles.Events; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; - -namespace Logitar.Identity.Domain.Roles; - -[Trait(Traits.Category, Categories.Unit)] -public class RoleAggregateTests -{ - private readonly Faker _faker = new(); - private readonly UniqueNameSettings _uniqueNameSettings = new(); - private readonly UniqueNameUnit _uniqueName; - private readonly RoleAggregate _role; - - public RoleAggregateTests() - { - _uniqueName = new(_uniqueNameSettings, "admin"); - _role = new(_uniqueName); - } - - [Fact(DisplayName = "ctor: it should create a new role with parameters.")] - public void ctor_it_should_create_a_new_role_with_parameters() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - ActorId actorId = ActorId.NewId(); - RoleId id = new(Guid.NewGuid().ToString()); - - RoleAggregate role = new(_uniqueName, tenantId, actorId, id); - - Assert.Equal(id, role.Id); - Assert.Equal(actorId, role.CreatedBy); - Assert.Equal(tenantId, role.TenantId); - Assert.Equal(_uniqueName, role.UniqueName); - } - - [Fact(DisplayName = "Delete: it should delete the role when it is not deleted.")] - public void Delete_it_should_delete_the_role_when_it_is_not_deleted() - { - Assert.False(_role.IsDeleted); - - _role.Delete(); - Assert.True(_role.IsDeleted); - Assert.Contains(_role.Changes, change => change is RoleDeletedEvent); - - _role.ClearChanges(); - _role.Delete(); - Assert.False(_role.HasChanges); - } - - [Fact(DisplayName = "Description: it should change the description when it is different.")] - public void Description_it_should_change_the_description_when_it_is_different() - { - DescriptionUnit description = new("This is the main administration role."); - _role.Description = description; - Assert.Equal(description, _role.Description); - - _role.Update(); - - _role.Description = description; - AssertHasNoUpdate(_role); - } - - [Fact(DisplayName = "DisplayName: it should change the display name when it is different.")] - public void DisplayName_it_should_change_the_display_name_when_it_is_different() - { - DisplayNameUnit displayName = new("Administrator"); - _role.DisplayName = displayName; - Assert.Equal(displayName, _role.DisplayName); - - _role.Update(); - - _role.DisplayName = displayName; - AssertHasNoUpdate(_role); - } - - [Fact(DisplayName = "RemoveCustomAttribute: it should remove an existing custom attribute.")] - public void RemoveCustomAttribute_it_should_remove_an_existing_custom_attribute() - { - string key = "remove_roles"; - - _role.SetCustomAttribute(key, bool.TrueString); - _role.Update(); - - _role.RemoveCustomAttribute($" {key} "); - _role.Update(); - Assert.False(_role.CustomAttributes.ContainsKey(key)); - - _role.RemoveCustomAttribute(key); - AssertHasNoUpdate(_role); - } - - [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute when it is different.")] - public void SetCustomAttribute_it_should_set_a_custom_attribute_when_it_is_different() - { - string key = " remove_roles "; - string value = $" {bool.TrueString} "; - _role.SetCustomAttribute(key, value); - Assert.Equal(value.Trim(), _role.CustomAttributes[key.Trim()]); - - _role.Update(); - - _role.SetCustomAttribute(key, value); - AssertHasNoUpdate(_role); - } - - [Fact(DisplayName = "SetCustomAttribute: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomAttribute_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _role.SetCustomAttribute(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _role.SetCustomAttribute(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _role.SetCustomAttribute("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _role.SetCustomAttribute("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "SetUniqueName: it should change the unique name when it is different.")] - public void SetUniqueName_it_should_change_the_unique_name_when_it_is_different() - { - UniqueNameUnit uniqueName = new(_uniqueNameSettings, "manage_users"); - _role.SetUniqueName(uniqueName); - Assert.Equal(uniqueName, _role.UniqueName); - Assert.Contains(_role.Changes, change => change is RoleUniqueNameChangedEvent); - - _role.ClearChanges(); - _role.SetUniqueName(uniqueName); - Assert.False(_role.HasChanges); - } - - [Fact(DisplayName = "ToString: it should return the correct string representation.")] - public void ToString_it_should_return_the_correct_string_representation() - { - Assert.StartsWith($"{_uniqueName.Value} | ", _role.ToString()); - - DisplayNameUnit displayName = new("Administrator"); - _role.DisplayName = displayName; - Assert.StartsWith($"{displayName.Value} | ", _role.ToString()); - } - - [Fact(DisplayName = "UniqueName: it should throw InvalidOperationException when it has not been initialized yet.")] - public void UniqueName_it_should_throw_InvalidOperationException_when_it_has_not_been_initialized_yet() - { - RoleAggregate role = new(); - Assert.Throws(() => _ = role.UniqueName); - } - - [Fact(DisplayName = "Update: it should update the role when it has changes.")] - public void Update_it_should_update_the_role_when_it_has_changes() - { - ActorId actorId = ActorId.NewId(); - - _role.DisplayName = new DisplayNameUnit("Administrator"); - _role.Update(actorId); - Assert.Equal(actorId, _role.UpdatedBy); - - long version = _role.Version; - _role.Update(actorId); - Assert.Equal(version, _role.Version); - } - - private static void AssertHasNoUpdate(RoleAggregate role) - { - FieldInfo? field = role.GetType().GetField("_updatedEvent", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - RoleUpdatedEvent? updated = field.GetValue(role) as RoleUpdatedEvent; - Assert.NotNull(updated); - Assert.False(updated.HasChanges); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleIdTests.cs deleted file mode 100644 index 5be3da8..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleIdTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; - -namespace Logitar.Identity.Domain.Roles; - -[Trait(Traits.Category, Categories.Unit)] -public class RoleIdTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "ctor: it should create an identifier from a Guid.")] - public void ctor_it_should_create_an_identifier_from_a_Guid() - { - Guid guid = Guid.NewGuid(); - RoleId id = new(guid); - string expected = new AggregateId(guid).Value; - Assert.Equal(expected, id.Value); - } - - [Theory(DisplayName = "ctor: it should create a new role identifier.")] - [InlineData("930a9c48-c168-47c8-9797-7106969dd7f7")] - [InlineData(" admin ")] - public void ctor_it_should_create_a_new_role_identifier(string value) - { - RoleId roleId = new(value); - Assert.Equal(value.Trim(), roleId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(RoleId); - - var exception = Assert.Throws(() => new RoleId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(RoleId); - - var exception = Assert.Throws(() => new RoleId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "NewId: it should create a new role ID.")] - public void NewId_it_should_create_a_new_role_Id() - { - RoleId id = RoleId.NewId(); - Assert.Equal(id.AggregateId.Value, id.Value); - } - - [Fact(DisplayName = "ToGuid: it should convert the identifier to a Guid.")] - public void ToGuid_it_should_convert_the_identifier_to_a_Guid() - { - Guid guid = Guid.NewGuid(); - RoleId id = new(new AggregateId(guid)); - Assert.Equal(guid, id.ToGuid()); - } - - [Theory(DisplayName = "TryCreate: it should return a role identifier when the value is not empty.")] - [InlineData("ede716c6-820a-4276-a3f9-d65645db7538")] - [InlineData(" admin ")] - public void TryCreate_it_should_return_a_role_identifier_when_the_value_is_not_empty(string value) - { - RoleId? roleId = RoleId.TryCreate(value); - Assert.NotNull(roleId); - Assert.Equal(value.Trim(), roleId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(RoleId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/Events/SessionUpdatedEventTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/Events/SessionUpdatedEventTests.cs deleted file mode 100644 index 4f952cf..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/Events/SessionUpdatedEventTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Sessions.Events; - -[Trait(Traits.Category, Categories.Unit)] -public class SessionUpdatedEventTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "It should be serializable and deserializable.")] - public void It_should_be_serializable_and_deserializable() - { - SessionUpdatedEvent @event = new(); - @event.CustomAttributes.Add("AdditionalInformation", $@"{{""User-Agent"":""{_faker.Internet.UserAgent()}""}}"); - @event.CustomAttributes.Add("IpAddress", _faker.Internet.Ip()); - - string json = JsonSerializer.Serialize(@event); - Assert.DoesNotContain("haschanges", json.ToLower()); - - SessionUpdatedEvent? deserialized = JsonSerializer.Deserialize(json); - Assert.NotNull(deserialized); - Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionAggregateTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionAggregateTests.cs deleted file mode 100644 index ac9a023..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionAggregateTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Sessions.Events; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; -using Logitar.Identity.Domain.Users; -using Logitar.Security.Cryptography; - -namespace Logitar.Identity.Domain.Sessions; - -[Trait(Traits.Category, Categories.Unit)] -public class SessionAggregateTests -{ - private readonly Faker _faker = new(); - private readonly UserAggregate _user; - private readonly SessionAggregate _session; - - public SessionAggregateTests() - { - _user = new(new UniqueNameUnit(new UniqueNameSettings(), _faker.Person.UserName)); - _session = new(_user); - } - - [Fact(DisplayName = "ctor: it should create a new session using the specified actor identifier.")] - public void ctor_it_should_create_a_new_session_using_the_specified_actor_identifier() - { - ActorId actorId = ActorId.NewId(); - SessionAggregate session = new(_user, actorId: actorId); - Assert.Contains(session.Changes, change => change is SessionCreatedEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "ctor: it should create a new session with parameters.")] - public void ctor_it_should_create_a_new_session_with_parameters() - { - string secretString = RandomStringGenerator.GetString(32); - Base64Password secret = new(secretString); - SessionId id = new(Guid.NewGuid().ToString()); - - SessionAggregate session = new(_user, secret, actorId: null, id); - - Assert.Equal(id, session.Id); - Assert.Equal(_user.Id.Value, session.CreatedBy.Value); - Assert.True(session.IsActive); - Assert.True(session.IsPersistent); - Assert.Equal(_user.Id, session.UserId); - AssertSecret(session, secretString); - } - - [Fact(DisplayName = "Delete: it should delete the session when it is not deleted.")] - public void Delete_it_should_delete_the_session_when_it_is_not_deleted() - { - Assert.False(_session.IsDeleted); - - _session.Delete(); - Assert.True(_session.IsDeleted); - Assert.Contains(_session.Changes, change => change is SessionDeletedEvent); - - _session.ClearChanges(); - _session.Delete(); - Assert.False(_session.HasChanges); - } - - [Fact(DisplayName = "RemoveCustomAttribute: it should remove an existing custom attribute.")] - public void RemoveCustomAttribute_it_should_remove_an_existing_custom_attribute() - { - string key = "remove_sessions"; - - _session.SetCustomAttribute(key, bool.TrueString); - _session.Update(); - - _session.RemoveCustomAttribute($" {key} "); - _session.Update(); - Assert.False(_session.CustomAttributes.ContainsKey(key)); - - _session.RemoveCustomAttribute(key); - AssertHasNoUpdate(_session); - } - - [Fact(DisplayName = "Renew: it should renew the session.")] - public void Renew_it_should_renew_the_session() - { - string oldSecretString = RandomStringGenerator.GetString(32); - Base64Password oldSecret = new(oldSecretString); - SessionAggregate session = new(_user, oldSecret); - - string newSecretString = RandomStringGenerator.GetString(32); - Base64Password newSecret = new(newSecretString); - - session.Renew(oldSecretString, newSecret); - Assert.Contains(session.Changes, change => change is SessionRenewedEvent @event - && @event.ActorId.Value == _user.Id.Value - && @event.Secret == newSecret); - } - - [Fact(DisplayName = "Renew: it should renew the session using the specified actor identifier.")] - public void Renew_it_should_renew_the_session_using_the_specified_actor_identifier() - { - string secretString = RandomStringGenerator.GetString(32); - Base64Password secret = new(secretString); - SessionAggregate session = new(_user, secret); - - ActorId actorId = ActorId.NewId(); - session.Renew(secretString, secret, actorId: actorId); - Assert.Contains(session.Changes, change => change is SessionRenewedEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "Renew: it should throw IncorrectSessionSecretException when the current secret is incorrect.")] - public void Renew_it_should_throw_IncorrectSessionSecretException_when_the_current_secret_is_incorrect() - { - string secretString = RandomStringGenerator.GetString(32); - Base64Password secret = new(secretString); - SessionAggregate session = new(_user, secret); - - string attemptedSecret = secretString[1..]; - var exception = Assert.Throws(() => session.Renew(attemptedSecret, secret)); - Assert.Equal(attemptedSecret, exception.AttemptedSecret); - Assert.Equal(session.Id, exception.SessionId); - } - - [Fact(DisplayName = "Renew: it should throw SessionIsNotActiveException when the session is not active.")] - public void Renew_it_should_throw_SessionIsNotActiveException_when_the_session_is_not_active() - { - string currentSecret = RandomStringGenerator.GetString(32); - Base64Password newSecret = new(currentSecret); - SessionAggregate session = new(_user, newSecret); - - session.SignOut(); - - var exception = Assert.Throws(() => session.Renew(currentSecret, newSecret)); - Assert.Equal(session.Id, exception.SessionId); - } - - [Fact(DisplayName = "Renew: it should throw SessionIsNotPersistentException when the session has no secret.")] - public void Renew_it_should_throw_SessionIsNotPersistentException_when_the_session_has_no_secret() - { - string secretString = RandomStringGenerator.GetString(32); - Base64Password newSecret = new(secretString); - - var exception = Assert.Throws(() => _session.Renew(secretString, newSecret)); - Assert.Equal(_session.Id, exception.SessionId); - } - - [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute when it is different.")] - public void SetCustomAttribute_it_should_set_a_custom_attribute_when_it_is_different() - { - string key = " remove_sessions "; - string value = $" {bool.TrueString} "; - _session.SetCustomAttribute(key, value); - Assert.Equal(value.Trim(), _session.CustomAttributes[key.Trim()]); - - _session.Update(); - - _session.SetCustomAttribute(key, value); - AssertHasNoUpdate(_session); - } - - [Fact(DisplayName = "SetCustomAttribute: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomAttribute_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _session.SetCustomAttribute(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _session.SetCustomAttribute(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _session.SetCustomAttribute("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _session.SetCustomAttribute("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "SignOut: it should sign-out the session if it is active.")] - public void SignOut_it_should_sign_out_the_session_if_it_is_active() - { - Assert.True(_session.IsActive); - - _session.SignOut(); - Assert.False(_session.IsActive); - Assert.Contains(_session.Changes, change => change is SessionSignedOutEvent @event && @event.ActorId.Value == _user.Id.Value); - - _session.ClearChanges(); - - _session.SignOut(); - Assert.False(_session.HasChanges); - } - - [Fact(DisplayName = "SignOut: it should sign-out the session using the specified actor identifier.")] - public void SignOut_it_should_sign_out_the_session_using_the_specified_actor_identifier() - { - ActorId actorId = ActorId.NewId(); - _session.SignOut(actorId); - Assert.Contains(_session.Changes, change => change is SessionSignedOutEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "Update: it should update the session when it has changes.")] - public void Update_it_should_update_the_session_when_it_has_changes() - { - ActorId actorId = ActorId.NewId(); - - _session.SetCustomAttribute("IpAddress", _faker.Internet.IpAddress().ToString()); - _session.Update(actorId); - Assert.Equal(actorId, _session.UpdatedBy); - - long version = _session.Version; - _session.Update(actorId); - Assert.Equal(version, _session.Version); - } - - private static void AssertHasNoUpdate(SessionAggregate session) - { - FieldInfo? field = session.GetType().GetField("_updatedEvent", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - SessionUpdatedEvent? updated = field.GetValue(session) as SessionUpdatedEvent; - Assert.NotNull(updated); - Assert.False(updated.HasChanges); - } - private static void AssertSecret(SessionAggregate session, string? secret) - { - FieldInfo? field = session.GetType().GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - Password? instance = field.GetValue(session) as Password; - if (secret == null) - { - Assert.Null(instance); - } - else - { - Assert.NotNull(instance); - Assert.True(instance.IsMatch(secret)); - } - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionIdTests.cs deleted file mode 100644 index 74137b5..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Sessions/SessionIdTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; - -namespace Logitar.Identity.Domain.Sessions; - -[Trait(Traits.Category, Categories.Unit)] -public class SessionIdTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "ctor: it should create an identifier from a Guid.")] - public void ctor_it_should_create_an_identifier_from_a_Guid() - { - Guid guid = Guid.NewGuid(); - SessionId id = new(guid); - string expected = new AggregateId(guid).Value; - Assert.Equal(expected, id.Value); - } - - [Theory(DisplayName = "ctor: it should create a new session identifier.")] - [InlineData("8b0a5032-f81f-4cef-99a6-de119025e379")] - [InlineData(" 702f0d94-a99c-4279-bc18-1ecc5762cf1d ")] - public void ctor_it_should_create_a_new_session_identifier(string value) - { - SessionId sessionId = new(value); - Assert.Equal(value.Trim(), sessionId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(SessionId); - - var exception = Assert.Throws(() => new SessionId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(SessionId); - - var exception = Assert.Throws(() => new SessionId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "NewId: it should create a new session ID.")] - public void NewId_it_should_create_a_new_session_Id() - { - SessionId id = SessionId.NewId(); - Assert.Equal(id.AggregateId.Value, id.Value); - } - - [Fact(DisplayName = "ToGuid: it should convert the identifier to a Guid.")] - public void ToGuid_it_should_convert_the_identifier_to_a_Guid() - { - Guid guid = Guid.NewGuid(); - SessionId id = new(new AggregateId(guid)); - Assert.Equal(guid, id.ToGuid()); - } - - [Theory(DisplayName = "TryCreate: it should return a session identifier when the value is not empty.")] - [InlineData("795d605e-258b-432a-bd83-be470d5d240d")] - [InlineData(" 2e7c19db-cda2-46df-a62f-81c8aeee83c6 ")] - public void TryCreate_it_should_return_a_session_identifier_when_the_value_is_not_empty(string value) - { - SessionId? sessionId = SessionId.TryCreate(value); - Assert.NotNull(sessionId); - Assert.Equal(value.Trim(), sessionId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(SessionId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DescriptionUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DescriptionUnitTests.cs deleted file mode 100644 index efbfc80..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DescriptionUnitTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FluentValidation; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class DescriptionUnitTests -{ - [Theory(DisplayName = "ctor: it should create a new description.")] - [InlineData("Description")] - [InlineData(" This is a description. ")] - public void ctor_it_should_create_a_new_description(string value) - { - DescriptionUnit description = new(value); - Assert.Equal(value.Trim(), description.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is not valid.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_not_valid(string value) - { - string propertyName = nameof(DescriptionUnit); - - var exception = Assert.Throws(() => new DescriptionUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Theory(DisplayName = "TryCreate: it should return a description when the value is not empty.")] - [InlineData("Description")] - [InlineData(" This is a description. ")] - public void TryCreate_it_should_return_a_description_when_the_value_is_not_empty(string value) - { - DescriptionUnit? description = DescriptionUnit.TryCreate(value); - Assert.NotNull(description); - Assert.Equal(value.Trim(), description.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(DescriptionUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DisplayNameUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DisplayNameUnitTests.cs deleted file mode 100644 index 02035ee..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/DisplayNameUnitTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class DisplayNameUnitTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new display name.")] - [InlineData("DisplayName")] - [InlineData(" This is a display name. ")] - public void ctor_it_should_create_a_new_display_name(string value) - { - DisplayNameUnit displayName = new(value); - Assert.Equal(value.Trim(), displayName.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(DisplayNameUnit); - - var exception = Assert.Throws(() => new DisplayNameUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(DisplayNameUnit.MaximumLength + 1); - string propertyName = nameof(DisplayNameUnit); - - var exception = Assert.Throws(() => new DisplayNameUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Theory(DisplayName = "TryCreate: it should return a display name when the value is not empty.")] - [InlineData("DisplayName")] - [InlineData(" This is a display name. ")] - public void TryCreate_it_should_return_a_display_name_when_the_value_is_not_empty(string value) - { - DisplayNameUnit? displayName = DisplayNameUnit.TryCreate(value); - Assert.NotNull(displayName); - Assert.Equal(value.Trim(), displayName.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(DisplayNameUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/LocaleUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/LocaleUnitTests.cs deleted file mode 100644 index 5005267..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/LocaleUnitTests.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class LocaleUnitTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new locale from a CultureInfo.")] - [InlineData("en-CA")] - [InlineData("en ")] - public void ctor_it_should_create_a_new_locale_from_a_CultureInfo(string value) - { - CultureInfo culture = CultureInfo.GetCultureInfo(value.Trim()); - LocaleUnit locale = new(culture); - - Assert.Equal(culture, locale.Culture); - Assert.Equal(culture.Name, locale.Code); - } - - [Theory(DisplayName = "ctor: it should create a new locale.")] - [InlineData("fr-CA")] - [InlineData("fr ")] - public void ctor_it_should_create_a_new_Locale_from_a_string(string value) - { - LocaleUnit locale = new(value); - - Assert.Equal(value.Trim(), locale.Code); - Assert.Equal(CultureInfo.GetCultureInfo(value.Trim()), locale.Culture); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(LocaleUnit); - - var exception = Assert.Throws(() => new LocaleUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.True(e.ErrorCode == "LocaleValidator" || e.ErrorCode == "NotEmptyValidator"); - }); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is not a valid locale code.")] - [InlineData("")] - [InlineData(" ")] - [InlineData("en-BE")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_not_a_valid_locale_code(string value) - { - string propertyName = nameof(LocaleUnit); - - var exception = Assert.Throws(() => new LocaleUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.True(e.ErrorCode == "LocaleValidator" || e.ErrorCode == "NotEmptyValidator"); - }); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is not a valid locale CultureInfo.")] - [InlineData("")] - [InlineData(" ")] - [InlineData("fr-MX")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_not_a_valid_locale_CultureInfo(string value) - { - string propertyName = nameof(LocaleUnit); - - var exception = Assert.Throws(() => new LocaleUnit(CultureInfo.GetCultureInfo(value.Trim()), propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.True(e.ErrorCode == "LocaleValidator" || e.ErrorCode == "NotEmptyValidator"); - }); - } - - [Theory(DisplayName = "Equals: two locales with the same code should be equal.")] - [InlineData("en")] - [InlineData("en-US")] - public void Equals_two_locales_with_the_same_code_should_be_equal(string code) - { - LocaleUnit left = new(code); - LocaleUnit right = new(CultureInfo.GetCultureInfo(code)); - - Assert.Equal(left, right); - Assert.True(left.Equals(right)); - Assert.True(left == right); - } - - [Theory(DisplayName = "TryCreate: it should return a locale when the value is not empty.")] - [InlineData("es-MX")] - [InlineData(" es")] - public void TryCreate_it_should_return_a_locale_when_the_value_is_not_empty(string value) - { - LocaleUnit? locale = LocaleUnit.TryCreate(value); - Assert.NotNull(locale); - - Assert.Equal(value.Trim(), locale.Code); - Assert.Equal(CultureInfo.GetCultureInfo(value.Trim()), locale.Culture); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(LocaleUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TenantIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TenantIdTests.cs deleted file mode 100644 index 5c435a8..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TenantIdTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class TenantIdTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new tenant identifier.")] - [InlineData("59e2fc4b-f4e4-4052-a3b0-2f4375964149")] - [InlineData(" 59e2fc4b-f4e4-4052-a3b0-2f4375964149 ")] - public void ctor_it_should_create_a_new_tenant_identifier(string value) - { - TenantId tenantId = new(value); - Assert.Equal(value.Trim(), tenantId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(TenantId); - - var exception = Assert.Throws(() => new TenantId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(TenantId); - - var exception = Assert.Throws(() => new TenantId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Theory(DisplayName = "TryCreate: it should return a tenant identifier when the value is not empty.")] - [InlineData("ede716c6-820a-4276-a3f9-d65645db7538")] - [InlineData(" test ")] - public void TryCreate_it_should_return_a_tenant_identifier_when_the_value_is_not_empty(string value) - { - TenantId? tenantId = TenantId.TryCreate(value); - Assert.NotNull(tenantId); - Assert.Equal(value.Trim(), tenantId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(TenantId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TimeZoneUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TimeZoneUnitTests.cs deleted file mode 100644 index 57cfe0b..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/TimeZoneUnitTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Bogus; -using NodaTime; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class TimeZoneUnitTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new time zone from a DateTimeZone.")] - [InlineData("America/New_York")] - [InlineData(" America/Montreal ")] - public void ctor_it_should_create_a_new_time_zone_from_a_DateTimeZone(string value) - { - DateTimeZone? tz = DateTimeZoneProviders.Tzdb.GetZoneOrNull(value.Trim()); - Assert.NotNull(tz); - TimeZoneUnit timeZone = new(tz); - - Assert.Equal(tz, timeZone.TimeZone); - Assert.Equal(tz.Id, timeZone.Id); - } - - [Theory(DisplayName = "ctor: it should create a new time zone.")] - [InlineData("America/New_York")] - [InlineData(" America/Montreal ")] - public void ctor_it_should_create_a_new_time_zone_from_a_string(string value) - { - TimeZoneUnit timeZone = new(value); - - Assert.Equal(value.Trim(), timeZone.Id); - Assert.Equal(DateTimeZoneProviders.Tzdb.GetZoneOrNull(value.Trim())?.Id, timeZone.Id); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is not a valid tz identifier.")] - [InlineData("")] - [InlineData(" ")] - [InlineData("America/Québec")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_not_a_valid_tz_identifier(string value) - { - string propertyName = nameof(TimeZoneUnit); - - var exception = Assert.Throws(() => new TimeZoneUnit(value, propertyName)); - Assert.Contains(exception.Errors, e => e.ErrorCode == "TimeZoneValidator"); - } - - [Theory(DisplayName = "Equals: two time zones with the same identifier should be equal.")] - [InlineData("America/Montreal")] - [InlineData("America/New_York")] - public void Equals_two_time_zones_with_the_same_identifier_should_be_equal(string id) - { - TimeZoneUnit left = new(id); - - DateTimeZone? dtz = DateTimeZoneProviders.Tzdb.GetZoneOrNull(id); - Assert.NotNull(dtz); - TimeZoneUnit right = new(dtz); - - Assert.Equal(left, right); - Assert.True(left.Equals(right)); - Assert.True(left == right); - } - - [Theory(DisplayName = "TryCreate: it should return a time zone when the value is not empty.")] - [InlineData("America/New_York")] - [InlineData(" America/Montreal ")] - public void TryCreate_it_should_return_a_time_zone_when_the_value_is_not_empty(string value) - { - TimeZoneUnit? timeZone = TimeZoneUnit.TryCreate(value); - Assert.NotNull(timeZone); - - Assert.Equal(value.Trim(), timeZone.Id); - Assert.Equal(DateTimeZoneProviders.Tzdb.GetZoneOrNull(value.Trim()), timeZone.TimeZone); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(TimeZoneUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UniqueNameUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UniqueNameUnitTests.cs deleted file mode 100644 index 9324865..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UniqueNameUnitTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Bogus; -using Logitar.Identity.Domain.Settings; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class UniqueNameUnitTests -{ - private readonly Faker _faker = new(); - private readonly UniqueNameSettings _uniqueNameSettings = new(); - - [Theory(DisplayName = "ctor: it should create a new unique name.")] - [InlineData("admin")] - [InlineData(" admin@test.com ")] - public void ctor_it_should_create_a_new_unique_name(string value) - { - UniqueNameUnit uniqueName = new(_uniqueNameSettings, value); - Assert.Equal(value.Trim(), uniqueName.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value contains characters that are not allowed.")] - [InlineData(" test user ")] - [InlineData("test:user")] - public void ctor_it_should_throw_ValidationException_when_the_value_contains_characters_that_are_not_allowed(string value) - { - string propertyName = nameof(UniqueNameUnit); - - var exception = Assert.Throws(() => new UniqueNameUnit(_uniqueNameSettings, value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("AllowedCharactersValidator", e.ErrorCode); - }); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(UniqueNameUnit); - - var exception = Assert.Throws(() => new UniqueNameUnit(_uniqueNameSettings, value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(UniqueNameUnit.MaximumLength + 1, minChar: 'a', maxChar: 'z'); - string propertyName = nameof(UniqueNameUnit); - - var exception = Assert.Throws(() => new UniqueNameUnit(_uniqueNameSettings, value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Theory(DisplayName = "TryCreate: it should return a unique name when the value is not empty.")] - [InlineData("admin")] - [InlineData(" admin@test.com ")] - public void TryCreate_it_should_return_a_unique_name_when_the_value_is_not_empty(string value) - { - UniqueNameUnit? uniqueName = UniqueNameUnit.TryCreate(_uniqueNameSettings, value); - Assert.NotNull(uniqueName); - Assert.Equal(value.Trim(), uniqueName.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(UniqueNameUnit.TryCreate(_uniqueNameSettings, value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UrlUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UrlUnitTests.cs deleted file mode 100644 index dc8be10..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Shared/UrlUnitTests.cs +++ /dev/null @@ -1,135 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Shared; - -[Trait(Traits.Category, Categories.Unit)] -public class UrlUnitTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new URL.")] - [InlineData("http://test.com")] - [InlineData(" https://www.test.com/ ")] - public void ctor_it_should_create_a_new_Url_from_a_string(string value) - { - UrlUnit url = new(value); - - string expected = value.Trim(); - if (!expected.EndsWith('/')) - { - expected = string.Concat(expected, '/'); - } - Assert.Equal(expected, url.Value); - Assert.Equal(new Uri(value.Trim()), url.Uri); - } - - [Theory(DisplayName = "ctor: it should create a new Url from an Uri.")] - [InlineData("http://test.com")] - [InlineData(" https://www.test.com/ ")] - public void ctor_it_should_create_a_new_Url_from_an_Uri(string value) - { - Uri uri = new(value.Trim()); - UrlUnit url = new(uri); - - Assert.Equal(uri, url.Uri); - Assert.Equal(uri.ToString(), url.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(UrlUnit); - - var exception = Assert.Throws(() => new UrlUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.True(e.ErrorCode == "NotEmptyValidator" || e.ErrorCode == "UrlValidator"); - }); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is not a valid URL.")] - [InlineData("")] - [InlineData(" ")] - [InlineData("/about")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_not_a_valid_Url(string value) - { - string propertyName = nameof(UrlUnit); - - var exception = Assert.Throws(() => new UrlUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.True(e.ErrorCode == "NotEmptyValidator" || e.ErrorCode == "UrlValidator"); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the string value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_string_value_is_too_long() - { - string uriString = string.Concat("https://www.", _faker.Internet.DomainName(), "?key=", _faker.Random.String(UrlUnit.MaximumLength, minChar: 'a', maxChar: 'z')); - string propertyName = nameof(UrlUnit); - - var exception = Assert.Throws(() => new UrlUnit(uriString, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the Uri value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_Uri_value_is_too_long() - { - string uriString = string.Concat("https://www.", _faker.Internet.DomainName(), "?key=", _faker.Random.String(UrlUnit.MaximumLength, minChar: 'a', maxChar: 'z')); - string propertyName = nameof(UrlUnit); - - var exception = Assert.Throws(() => new UrlUnit(new Uri(uriString), propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Theory(DisplayName = "Equals: two URLs with the same value should be equal.")] - [InlineData("https://www.test.com/")] - [InlineData("https://www.test.com/projects/123?source=google#sub")] - public void Equals_two_Urls_with_the_same_value_should_be_equal(string value) - { - UrlUnit left = new(value); - UrlUnit right = new(new Uri(value)); - - Assert.Equal(left, right); - Assert.True(left.Equals(right)); - Assert.True(left == right); - } - - [Theory(DisplayName = "TryCreate: it should return a URL when the value is not empty.")] - [InlineData("http://test.com")] - [InlineData(" https://www.test.com/ ")] - public void TryCreate_it_should_return_a_Url_when_the_value_is_not_empty(string value) - { - UrlUnit? url = UrlUnit.TryCreate(value); - Assert.NotNull(url); - - string expected = value.Trim(); - if (!expected.EndsWith('/')) - { - expected = string.Concat(expected, '/'); - } - Assert.Equal(expected, url.Value); - Assert.Equal(new Uri(value.Trim()), url.Uri); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(UrlUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/Events/UserUpdatedEventTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Users/Events/UserUpdatedEventTests.cs deleted file mode 100644 index dc22643..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/Events/UserUpdatedEventTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Users.Events; - -[Trait(Traits.Category, Categories.Unit)] -public class UserUpdatedEventTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "It should be serializable and deserializable.")] - public void It_should_be_serializable_and_deserializable() - { - UserUpdatedEvent @event = new(); - @event.CustomAttributes.Add("HealthInsuranceNumber", _faker.Person.BuildHealthInsuranceNumber()); - @event.CustomAttributes.Add("JobTitle", "Sales Manager"); - - string json = JsonSerializer.Serialize(@event); - Assert.DoesNotContain("haschanges", json.ToLower()); - - UserUpdatedEvent? deserialized = JsonSerializer.Deserialize(json); - Assert.NotNull(deserialized); - Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonHelperTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonHelperTests.cs deleted file mode 100644 index 15599d6..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonHelperTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Users; - -[Trait(Traits.Category, Categories.Unit)] -public class PersonHelperTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "BuildFullNameString: it should return null when the list is empty.")] - public void BuildFullNameString_it_should_return_null_when_the_list_is_empty() - { - Assert.Null(PersonHelper.BuildFullName(Array.Empty())); - } - - [Fact(DisplayName = "BuildFullNameString: it should return null when the list only contains empty names.")] - public void BuildFullNameString_it_should_return_null_when_the_list_only_contains_empty_names() - { - Assert.Null(PersonHelper.BuildFullName(["", " "])); - } - - [Fact(DisplayName = "BuildFullNameString: it should build the full name of a person.")] - public void BuildFullNameString_it_should_build_the_full_name_of_a_person() - { - string[] names = [_faker.Name.FirstName(), $" {_faker.Name.FirstName()} ", _faker.Name.LastName()]; - string expected = string.Join(' ', names.Select(name => name.Trim())); - Assert.Equal(expected, PersonHelper.BuildFullName(names)); - } - - [Fact(DisplayName = "BuildFullNameUnit: it should return null when the list is empty.")] - public void BuildFullNameUnit_it_should_return_null_when_the_list_is_empty() - { - Assert.Null(PersonHelper.BuildFullName(Array.Empty())); - } - - [Fact(DisplayName = "BuildFullNameUnit: it should build the full name of a person.")] - public void BuildFullNameUnit_it_should_build_the_full_name_of_a_person() - { - string[] names = [_faker.Name.FirstName(), $" {_faker.Name.FirstName()} ", _faker.Name.LastName()]; - string expected = string.Join(' ', names.Select(name => name.Trim())); - Assert.Equal(expected, PersonHelper.BuildFullName(names.Select(name => new PersonNameUnit(name)).ToArray())); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonNameUnitTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonNameUnitTests.cs deleted file mode 100644 index 54d3eba..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PersonNameUnitTests.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Bogus; - -namespace Logitar.Identity.Domain.Users; - -[Trait(Traits.Category, Categories.Unit)] -public class PersonNameUnitTests -{ - private readonly Faker _faker = new(); - - [Theory(DisplayName = "ctor: it should create a new person name.")] - [InlineData("PersonName")] - [InlineData(" This is a person name. ")] - public void ctor_it_should_create_a_new_person_name(string value) - { - PersonNameUnit personName = new(value); - Assert.Equal(value.Trim(), personName.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(PersonNameUnit); - - var exception = Assert.Throws(() => new PersonNameUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(PersonNameUnit.MaximumLength + 1); - string propertyName = nameof(PersonNameUnit); - - var exception = Assert.Throws(() => new PersonNameUnit(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Theory(DisplayName = "TryCreate: it should return a person name when the value is not empty.")] - [InlineData("PersonName")] - [InlineData(" This is a person name. ")] - public void TryCreate_it_should_return_a_person_name_when_the_value_is_not_empty(string value) - { - PersonNameUnit? personName = PersonNameUnit.TryCreate(value); - Assert.NotNull(personName); - Assert.Equal(value.Trim(), personName.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(PersonNameUnit.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserAggregateTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserAggregateTests.cs deleted file mode 100644 index 765b6a4..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserAggregateTests.cs +++ /dev/null @@ -1,789 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Roles; -using Logitar.Identity.Domain.Sessions; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; -using Logitar.Identity.Domain.Users.Events; -using Logitar.Security.Cryptography; - -namespace Logitar.Identity.Domain.Users; - -[Trait(Traits.Category, Categories.Unit)] -public class UserAggregateTests -{ - private const string PasswordString = "Test123!"; - - private readonly Faker _faker = new(); - private readonly UniqueNameSettings _uniqueNameSettings = new(); - private readonly UniqueNameUnit _uniqueName; - private readonly RoleAggregate _role; - private readonly UserAggregate _user; - - public UserAggregateTests() - { - _uniqueName = new(_uniqueNameSettings, _faker.Person.UserName); - _role = new(new UniqueNameUnit(_uniqueNameSettings, "admin")); - _user = new(_uniqueName); - } - - [Fact(DisplayName = "AddRole: it should add the role to the user when it does not have the role.")] - public void AddRole_it_should_add_the_role_to_the_user_when_it_does_not_have_the_role() - { - Assert.False(_user.HasRole(_role)); - Assert.Empty(_user.Roles); - - _user.AddRole(_role); - Assert.Contains(_user.Changes, changes => changes is UserRoleAddedEvent @event && @event.RoleId == _role.Id); - Assert.True(_user.HasRole(_role)); - Assert.Single(_user.Roles, _role.Id); - - _user.ClearChanges(); - _user.AddRole(_role); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "AddRole: it should throw TenantMismatchException when the role is in a different tenant.")] - public void AddRole_it_should_throw_TenantMismatchException_when_the_role_is_in_a_different_tenant() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - RoleAggregate role = new(_role.UniqueName, tenantId); - - var exception = Assert.Throws(() => _user.AddRole(role)); - Assert.Equal(_user.TenantId, exception.ExpectedTenantId); - Assert.Equal(role.TenantId, exception.ActualTenantId); - } - - [Fact(DisplayName = "Authenticate: it should authenticate the user.")] - public void Authenticate_it_should_authenticate_the_user() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - - _user.Authenticate(PasswordString); - - UserAuthenticatedEvent @event = (UserAuthenticatedEvent)Assert.Single(_user.Changes, change => change is UserAuthenticatedEvent); - Assert.Equal(_user.Id.Value, @event.ActorId.Value); - Assert.Equal(@event.OccurredOn, _user.AuthenticatedOn); - } - - [Fact(DisplayName = "Authenticate: it should authenticate the user using the specified actor identifier.")] - public void Authenticate_it_should_authenticate_the_user_using_the_specified_actor_identifier() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - - ActorId actorId = ActorId.NewId(); - _user.Authenticate(PasswordString, actorId); - Assert.Contains(_user.Changes, change => change is UserAuthenticatedEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "Authenticate: it should throw IncorrectUserPasswordException when the attempted password is incorrect.")] - public void Authenticate_it_should_throw_IncorrectUserPasswordException_when_the_attempted_password_is_incorrect() - { - Base64Password password = new(PasswordString[1..]); - _user.SetPassword(password); - - var exception = Assert.Throws(() => _user.Authenticate(PasswordString)); - Assert.Equal(PasswordString, exception.AttemptedPassword); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "Authenticate: it should throw UserHasNoPasswordException when the user has no password.")] - public void Authenticate_it_should_throw_UserHasNoPasswordException_when_the_user_has_no_password() - { - var exception = Assert.Throws(() => _user.Authenticate(PasswordString)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "Authenticate: it should throw UserIsDisabledException when the user is disabled.")] - public void Authenticate_it_should_throw_UserIsDisabledException_when_the_user_is_disabled() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - _user.Disable(); - - var exception = Assert.Throws(() => _user.Authenticate(PasswordString)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "Birthdate: it should change the birthdate when it is different.")] - public void Birthdate_it_should_change_the_birthdate_when_it_is_different() - { - DateTime birthdate = _faker.Person.DateOfBirth; - - _user.Birthdate = birthdate; - Assert.Equal(birthdate, _user.Birthdate); - - _user.Update(); - - _user.Birthdate = birthdate; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Birthdate: it should throw ValidationException when the value is in the future.")] - public void Birthdate_it_should_throw_ValidationException_when_the_value_is_in_the_future() - { - DateTime birthdate = DateTime.Now.AddYears(1); - var exception = Assert.Throws(() => _user.Birthdate = birthdate); - Assert.All(exception.Errors, e => - { - Assert.Equal("PastValidator", e.ErrorCode); - Assert.Equal(nameof(_user.Birthdate), e.PropertyName); - }); - } - - [Fact(DisplayName = "ChangePassword: it should change the users password.")] - public void ChangePassword_it_should_change_the_users_password() - { - string oldPassword = PasswordString[1..]; - _user.SetPassword(new Base64Password(oldPassword)); - _user.ClearChanges(); - AssertPassword(_user, oldPassword); - - Base64Password newPassword = new(PasswordString); - _user.ChangePassword(oldPassword, newPassword); - AssertPassword(_user, PasswordString); - Assert.Contains(_user.Changes, change => change is UserPasswordChangedEvent @event - && @event.ActorId.Value == _user.Id.Value - && @event.Password.IsMatch(PasswordString)); - } - - [Fact(DisplayName = "ChangePassword: it should change the users password using the specified actor identifier.")] - public void ChangePassword_it_should_change_the_users_password_using_the_specified_actor_identifier() - { - string oldPassword = PasswordString[1..]; - _user.SetPassword(new Base64Password(oldPassword)); - _user.ClearChanges(); - AssertPassword(_user, oldPassword); - - Base64Password newPassword = new(PasswordString); - ActorId actorId = ActorId.NewId(); - _user.ChangePassword(oldPassword, newPassword, actorId); - AssertPassword(_user, PasswordString); - Assert.Contains(_user.Changes, change => change is UserPasswordChangedEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "ChangePassword: it should throw IncorrectUserPasswordException when the attempted password is incorrect.")] - public void ChangePassword_it_should_throw_IncorrectUserPasswordException_when_the_attempted_password_is_incorrect() - { - _user.SetPassword(new Base64Password(PasswordString)); - AssertPassword(_user, PasswordString); - - Base64Password password = new(PasswordString); - string attemptedPassword = PasswordString[1..]; - var exception = Assert.Throws(() => _user.ChangePassword(attemptedPassword, password)); - Assert.Equal(attemptedPassword, exception.AttemptedPassword); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "ChangePassword: it should throw UserHasNoPasswordException when the user has no password.")] - public void ChangePassword_it_should_throw_UserHasNoPasswordException_when_the_user_has_no_password() - { - AssertPassword(_user, password: null); - - Base64Password password = new(PasswordString); - var exception = Assert.Throws(() => _user.ChangePassword(PasswordString, password)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "ChangePassword: it should throw UserIsDisabledException when the user is disabled.")] - public void ChangePassword_it_should_throw_UserIsDisabledException_when_the_user_is_disabled() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - - _user.Disable(); - - var exception = Assert.Throws(() => _user.ChangePassword(PasswordString, password)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "ctor: it should create a new user with parameters.")] - public void ctor_it_should_create_a_new_user_with_parameters() - { - TenantId tenantId = new(Guid.NewGuid().ToString()); - ActorId actorId = ActorId.NewId(); - UserId id = new(Guid.NewGuid().ToString()); - - UserAggregate user = new(_uniqueName, tenantId, actorId, id); - - Assert.Equal(id, user.Id); - Assert.Equal(actorId, user.CreatedBy); - Assert.Equal(tenantId, user.TenantId); - Assert.Equal(_uniqueName, user.UniqueName); - } - - [Fact(DisplayName = "Delete: it should delete the user when it is not deleted.")] - public void Delete_it_should_delete_the_user_when_it_is_not_deleted() - { - Assert.False(_user.IsDeleted); - - _user.Delete(); - Assert.True(_user.IsDeleted); - Assert.Contains(_user.Changes, change => change is UserDeletedEvent); - - _user.ClearChanges(); - _user.Delete(); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "Disable: it should disable the user when it is enabled.")] - public void Disable_it_should_disable_the_user_when_it_is_enabled() - { - Assert.False(_user.IsDisabled); - - _user.Disable(); - Assert.True(_user.IsDisabled); - Assert.Contains(_user.Changes, change => change is UserDisabledEvent); - - _user.ClearChanges(); - _user.Disable(); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "Enable: it should enable the user when it is disabled.")] - public void Enable_it_should_enable_the_user_when_it_is_disabled() - { - _user.Disable(); - Assert.True(_user.IsDisabled); - - _user.ClearChanges(); - - _user.Enable(); - Assert.False(_user.IsDisabled); - Assert.Contains(_user.Changes, change => change is UserEnabledEvent); - - _user.ClearChanges(); - _user.Enable(); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "FirstName: it should change the first name when it is different.")] - public void FirstName_it_should_change_the_first_name_when_it_is_different() - { - PersonNameUnit firstName = new(_faker.Person.FirstName); - _user.FirstName = firstName; - Assert.Equal(firstName, _user.FirstName); - - _user.Update(); - - _user.FirstName = firstName; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Gender: it should change the gender when it is different.")] - public void Gender_it_should_change_the_gender_when_it_is_different() - { - GenderUnit gender = new(_faker.Person.Gender.ToString()); - - _user.Gender = gender; - Assert.Equal(gender, _user.Gender); - - _user.Update(); - - _user.Gender = gender; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "HasPassword: it should return false when the user has no password.")] - public void HasPassword_it_should_return_false_when_the_user_has_no_password() - { - AssertPassword(_user, password: null); - Assert.False(_user.HasPassword); - } - - [Fact(DisplayName = "HasPassword: it should return true when the user has a password.")] - public void HasPassword_it_should_return_true_when_the_user_has_a_password() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - Assert.True(_user.HasPassword); - } - - [Fact(DisplayName = "HasRole: it should return false when the Api key does not have the specified role.")] - public void HasRole_it_should_return_false_when_the_Api_key_does_not_have_the_specified_role() - { - Assert.False(_user.HasRole(_role)); - } - - [Fact(DisplayName = "HasRole: it should return true when the Api key does have the specified role.")] - public void HasRole_it_should_return_true_when_the_Api_key_does_have_the_specified_role() - { - _user.AddRole(_role); - Assert.True(_user.HasRole(_role)); - } - - [Fact(DisplayName = "IsConfirmed: it should be false when the user has no verified contact information.")] - public void IsConfirmed_it_should_be_false_when_the_user_has_no_verified_contact_information() - { - _user.SetAddress(new AddressUnit("150 Saint-Catherine St W", "Montreal", "CA", "QC", "H2X 3Y2", isVerified: false)); - _user.SetEmail(new EmailUnit(_faker.Person.Email, isVerified: false)); - _user.SetPhone(new PhoneUnit("+15148454636", "CA", "12345", isVerified: false)); - Assert.False(_user.IsConfirmed); - } - - [Fact(DisplayName = "IsConfirmed: it should be true when the user has at least one verified contact information.")] - public void IsConfirmed_it_should_be_true_when_the_user_has_at_least_one_verified_contact_information() - { - _user.SetAddress(new AddressUnit("150 Saint-Catherine St W", "Montreal", "CA", "QC", "H2X 3Y2", isVerified: false)); - _user.SetEmail(new EmailUnit(_faker.Person.Email, isVerified: true)); - _user.SetPhone(new PhoneUnit("+15148454636", "CA", "12345", isVerified: false)); - Assert.True(_user.IsConfirmed); - } - - [Fact(DisplayName = "LastName: it should change the last name when it is different.")] - public void LastName_it_should_change_the_last_name_when_it_is_different() - { - PersonNameUnit lastName = new(_faker.Person.LastName); - _user.LastName = lastName; - Assert.Equal(lastName, _user.LastName); - - _user.Update(); - - _user.LastName = lastName; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Locale: it should change the locale when it is different.")] - public void Locale_it_should_change_the_locale_when_it_is_different() - { - LocaleUnit locale = new(_faker.Locale); - - _user.Locale = locale; - Assert.Equal(locale, _user.Locale); - - _user.Update(); - - _user.Locale = locale; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "MiddleName: it should change the last name when it is different.")] - public void MiddleName_it_should_change_the_last_name_when_it_is_different() - { - PersonNameUnit middleName = new(_faker.Name.FirstName(_faker.Person.Gender)); - _user.MiddleName = middleName; - Assert.Equal(middleName, _user.MiddleName); - - _user.Update(); - - _user.MiddleName = middleName; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Nickname: it should change the last name when it is different.")] - public void Nickname_it_should_change_the_last_name_when_it_is_different() - { - PersonNameUnit nickname = new(string.Concat(_faker.Person.FirstName.First(), _faker.Person.LastName).ToLower()); - _user.Nickname = nickname; - Assert.Equal(nickname, _user.Nickname); - - _user.Update(); - - _user.Nickname = nickname; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Picture: it should change the picture when it is different.")] - public void Picture_it_should_change_the_picture_when_it_is_different() - { - UrlUnit picture = new(_faker.Person.Avatar); - _user.Picture = picture; - Assert.Equal(picture, _user.Picture); - - _user.Update(); - - _user.Picture = picture; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "Profile: it should change the profile page when it is different.")] - public void Profile_it_should_change_the_profile_page_when_it_is_different() - { - UrlUnit profile = new($"https://www.test.com/employees/{_user.UniqueName.Value}"); - _user.Profile = profile; - Assert.Equal(profile, _user.Profile); - - _user.Update(); - - _user.Profile = profile; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "RemoveCustomAttribute: it should remove an existing custom attribute.")] - public void RemoveCustomAttribute_it_should_remove_an_existing_custom_attribute() - { - string key = "remove_users"; - - _user.SetCustomAttribute(key, bool.TrueString); - _user.Update(); - - _user.RemoveCustomAttribute($" {key} "); - _user.Update(); - Assert.False(_user.CustomAttributes.ContainsKey(key)); - - _user.RemoveCustomAttribute(key); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "RemoveCustomIdentifier: it should remove an existing custom identifier.")] - public void RemoveCustomIdentifier_it_should_remove_an_existing_custom_identifier() - { - string key = "google_id"; - - _user.SetCustomIdentifier(key, bool.TrueString); - _user.Update(); - - _user.RemoveCustomIdentifier($" {key} "); - _user.Update(); - Assert.False(_user.CustomIdentifiers.ContainsKey(key)); - - _user.RemoveCustomIdentifier(key); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "RemoveRole: it should remove the role from the user when it has the role.")] - public void RemoveRole_it_should_remove_the_role_from_the_user_when_it_has_the_role() - { - _user.AddRole(_role); - Assert.True(_user.HasRole(_role)); - Assert.Single(_user.Roles, _role.Id); - _user.ClearChanges(); - - _user.RemoveRole(_role); - Assert.Contains(_user.Changes, changes => changes is UserRoleRemovedEvent @event && @event.RoleId == _role.Id); - Assert.False(_user.HasRole(_role)); - Assert.Empty(_user.Roles); - - _user.ClearChanges(); - _user.RemoveRole(_role); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "ResetPassword: it should reset the user's password.")] - public void ResetPassword_it_should_reset_the_users_password() - { - Base64Password password = new(PasswordString); - _user.ResetPassword(password); - AssertPassword(_user, PasswordString); - Assert.Contains(_user.Changes, change => change is UserPasswordResetEvent @event - && @event.ActorId.Value == _user.Id.Value - && @event.Password.IsMatch(PasswordString)); - } - - [Fact(DisplayName = "ResetPassword: it should reset the user's password using the specified actor identifier.")] - public void ResetPassword_it_should_reset_the_users_password_using_the_specified_actor_identifier() - { - Base64Password password = new(PasswordString); - ActorId actorId = ActorId.NewId(); - _user.ResetPassword(password, actorId); - Assert.Contains(_user.Changes, change => change is UserPasswordResetEvent @event && @event.ActorId == actorId); - } - - [Fact(DisplayName = "ResetPassword: it should throw UserIsDisabledException when the user is disabled.")] - public void ResetPassword_it_should_throw_UserIsDisabledException_when_the_user_is_disabled() - { - _user.Disable(); - - Base64Password password = new(PasswordString); - var exception = Assert.Throws(() => _user.ResetPassword(password)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "SetAddress: it should change the postal address when it is different.")] - public void SetAddress_it_should_change_the_postal_address_when_it_is_different() - { - AddressUnit address = new("150 Saint-Catherine St W", "Montreal", "CA", "QC", "H2X 3Y2"); - _user.SetAddress(address); - Assert.Equal(address, _user.Address); - - _user.Update(); - - _user.SetAddress(address); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute when it is different.")] - public void SetCustomAttribute_it_should_set_a_custom_attribute_when_it_is_different() - { - string key = " remove_users "; - string value = $" {bool.TrueString} "; - _user.SetCustomAttribute(key, value); - Assert.Equal(value.Trim(), _user.CustomAttributes[key.Trim()]); - - _user.Update(); - - _user.SetCustomAttribute(key, value); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "SetCustomAttribute: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomAttribute_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _user.SetCustomAttribute(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _user.SetCustomAttribute(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _user.SetCustomAttribute("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _user.SetCustomAttribute("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "SetCustomIdentifier: it should set a custom identifier when it is different.")] - public void SetCustomIdentifier_it_should_set_a_custom_identifier_when_it_is_different() - { - string key = " google_id "; - string value = $" {Guid.NewGuid()} "; - _user.SetCustomIdentifier(key, value); - Assert.Equal(value.Trim(), _user.CustomIdentifiers[key.Trim()]); - - _user.Update(); - - _user.SetCustomIdentifier(key, value); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "SetCustomIdentifier: it should throw ValidationException when the key or value is not valid.")] - public void SetCustomIdentifier_it_should_throw_ValidationException_when_the_key_or_value_is_not_valid() - { - var exception = Assert.Throws(() => _user.SetCustomIdentifier(" ", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - string key = _faker.Random.String(IdentifierValidator.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - exception = Assert.Throws(() => _user.SetCustomIdentifier(key, "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _user.SetCustomIdentifier("-key", "value")); - Assert.All(exception.Errors, e => Assert.Equal("Key", e.PropertyName)); - - exception = Assert.Throws(() => _user.SetCustomIdentifier("key", " ")); - Assert.All(exception.Errors, e => Assert.Equal("Value", e.PropertyName)); - } - - [Fact(DisplayName = "SetEmail: it should change the email address when it is different.")] - public void SetEmail_it_should_change_the_email_address_when_it_is_different() - { - EmailUnit email = new(_faker.Person.Email); - _user.SetEmail(email); - Assert.Equal(email, _user.Email); - - _user.Update(); - - _user.SetEmail(email); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "SetPassword: it should change the password.")] - public void SetPassword_it_should_change_the_password() - { - AssertPassword(_user, password: null); - - ActorId actorId = ActorId.NewId(); - Base64Password password = new(PasswordString); - _user.SetPassword(password, actorId); - - AssertPassword(_user, PasswordString); - Assert.Contains(_user.Changes, change => change is UserPasswordUpdatedEvent @event - && @event.ActorId == actorId - && @event.Password.IsMatch(PasswordString)); - } - - [Fact(DisplayName = "SetPhone: it should change the phone number when it is different.")] - public void SetPhone_it_should_change_the_phone_number_when_it_is_different() - { - PhoneUnit phone = new("+15148454636", "CA", "12345"); - _user.SetPhone(phone); - Assert.Equal(phone, _user.Phone); - - _user.Update(); - - _user.SetPhone(phone); - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "SetUniqueName: it should change the unique name when it is different.")] - public void SetUniqueName_it_should_change_the_unique_name_when_it_is_different() - { - UniqueNameUnit uniqueName = new(_uniqueNameSettings, _faker.Internet.UserName()); - _user.SetUniqueName(uniqueName); - Assert.Equal(uniqueName, _user.UniqueName); - Assert.Contains(_user.Changes, change => change is UserUniqueNameChangedEvent); - - _user.ClearChanges(); - _user.SetUniqueName(uniqueName); - Assert.False(_user.HasChanges); - } - - [Fact(DisplayName = "SignIn: it should open a new session with a password check.")] - public void SignIn_it_should_open_a_new_session_with_a_password_check() - { - Base64Password password = new(PasswordString); - _user.SetPassword(password); - - string secretString = RandomStringGenerator.GetString(32); - Base64Password secret = new(secretString); - ActorId actorId = ActorId.NewId(); - SessionId sessionId = new(Guid.NewGuid().ToString()); - SessionAggregate session = _user.SignIn(PasswordString, secret, actorId, sessionId); - Assert.Equal(_user.Id, session.UserId); - Assert.True(session.IsActive); - Assert.True(session.IsPersistent); - - Assert.Equal(sessionId, session.Id); - Assert.Equal(actorId, session.CreatedBy); - AssertSecret(session, secretString); - - Assert.Contains(_user.Changes, change => change is UserSignedInEvent @event - && @event.ActorId == actorId - && @event.OccurredOn == session.CreatedOn); - } - - [Fact(DisplayName = "SignIn: it should open a new session without a password check.")] - public void SignIn_it_should_open_a_new_session_without_a_password_check() - { - SessionAggregate session = _user.SignIn(); - Assert.Equal(_user.Id, session.UserId); - Assert.True(session.IsActive); - Assert.False(session.IsPersistent); - - Assert.Equal(_user.Id.Value, session.CreatedBy.Value); - Assert.Contains(_user.Changes, change => change is UserSignedInEvent @event - && @event.ActorId.Value == _user.Id.Value - && @event.OccurredOn == session.CreatedOn); - } - - [Fact(DisplayName = "SignIn: it should throw IncorrectUserPasswordException when the password is incorrect.")] - public void SignIn_it_should_throw_IncorrectUserPasswordException_when_the_password_is_incorrect() - { - Base64Password password = new(PasswordString[1..]); - _user.SetPassword(password); - - var exception = Assert.Throws(() => _user.SignIn(PasswordString)); - Assert.Equal(PasswordString, exception.AttemptedPassword); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "SignIn: it should throw UserHasNoPasswordException when the user has no password.")] - public void SignIn_it_should_throw_UserHasNoPasswordException_when_the_user_has_no_password() - { - var exception = Assert.Throws(() => _user.SignIn(PasswordString)); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "SignIn: it should throw UserIsDisabledException when the user is disabled.")] - public void SignIn_it_should_throw_UserIsDisabledException_when_the_user_is_disabled() - { - _user.Disable(); - - var exception = Assert.Throws(() => _user.SignIn()); - Assert.Equal(_user.Id, exception.UserId); - } - - [Fact(DisplayName = "TimeZone: it should change the time zone when it is different.")] - public void TimeZone_it_should_change_the_time_zone_when_it_is_different() - { - TimeZoneUnit timeZone = new("America/New_York"); - - _user.TimeZone = timeZone; - Assert.Equal(timeZone, _user.TimeZone); - - _user.Update(); - - _user.TimeZone = timeZone; - AssertHasNoUpdate(_user); - } - - [Fact(DisplayName = "ToString: it should return the correct string representation.")] - public void ToString_it_should_return_the_correct_string_representation() - { - Assert.StartsWith($"{_uniqueName.Value} | ", _user.ToString()); - - _user.FirstName = new PersonNameUnit(" Charles-François "); - _user.LastName = new PersonNameUnit("Henry Angers"); - Assert.StartsWith("Charles-François Henry Angers | ", _user.ToString()); - } - - [Fact(DisplayName = "UniqueName: it should throw InvalidOperationException when it has not been initialized yet.")] - public void UniqueName_it_should_throw_InvalidOperationException_when_it_has_not_been_initialized_yet() - { - UserAggregate user = new(); - Assert.Throws(() => _ = user.UniqueName); - } - - [Fact(DisplayName = "Update: it should update the user when it has changes.")] - public void Update_it_should_update_the_user_when_it_has_changes() - { - ActorId actorId = ActorId.NewId(); - - _user.FirstName = new PersonNameUnit(_faker.Person.FirstName); - _user.LastName = new PersonNameUnit(_faker.Person.LastName); - _user.Update(actorId); - Assert.Equal(actorId, _user.UpdatedBy); - - long version = _user.Version; - _user.Update(actorId); - Assert.Equal(version, _user.Version); - } - - [Fact(DisplayName = "Website: it should change the website when it is different.")] - public void Website_it_should_change_the_website_when_it_is_different() - { - UrlUnit website = new($"https://www.{_faker.Person.Website}"); - _user.Website = website; - Assert.Equal(website, _user.Website); - - _user.Update(); - - _user.Website = website; - AssertHasNoUpdate(_user); - } - - private static void AssertHasNoUpdate(UserAggregate user) - { - FieldInfo? field = user.GetType().GetField("_updatedEvent", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - UserUpdatedEvent? updated = field.GetValue(user) as UserUpdatedEvent; - Assert.NotNull(updated); - Assert.False(updated.HasChanges); - } - private static void AssertPassword(UserAggregate user, string? password) - { - FieldInfo? field = user.GetType().GetField("_password", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - Password? instance = field.GetValue(user) as Password; - if (password == null) - { - Assert.Null(instance); - } - else - { - Assert.NotNull(instance); - Assert.True(instance.IsMatch(password)); - } - } - private static void AssertSecret(SessionAggregate session, string? secret) - { - FieldInfo? field = session.GetType().GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(field); - - Password? instance = field.GetValue(session) as Password; - if (secret == null) - { - Assert.Null(instance); - } - else - { - Assert.NotNull(instance); - Assert.True(instance.IsMatch(secret)); - } - } -} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserIdTests.cs b/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserIdTests.cs deleted file mode 100644 index b8e1d7b..0000000 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserIdTests.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Bogus; -using Logitar.EventSourcing; - -namespace Logitar.Identity.Domain.Users; - -[Trait(Traits.Category, Categories.Unit)] -public class UserIdTests -{ - private readonly Faker _faker = new(); - - [Fact(DisplayName = "ctor: it should create an identifier from a Guid.")] - public void ctor_it_should_create_an_identifier_from_a_Guid() - { - Guid guid = Guid.NewGuid(); - UserId id = new(guid); - string expected = new AggregateId(guid).Value; - Assert.Equal(expected, id.Value); - } - - [Theory(DisplayName = "ctor: it should create a new user identifier.")] - [InlineData("86f2b595-a803-40c5-b270-f33ea53620b0")] - [InlineData(" admin ")] - public void ctor_it_should_create_a_new_user_identifier(string value) - { - UserId userId = new(value); - Assert.Equal(value.Trim(), userId.Value); - } - - [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] - [InlineData("")] - [InlineData(" ")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) - { - string propertyName = nameof(UserId); - - var exception = Assert.Throws(() => new UserId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal(propertyName, e.PropertyName); - Assert.Equal("NotEmptyValidator", e.ErrorCode); - }); - } - - [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] - public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() - { - string value = _faker.Random.String(AggregateId.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - string propertyName = nameof(UserId); - - var exception = Assert.Throws(() => new UserId(value, propertyName)); - Assert.All(exception.Errors, e => - { - Assert.Equal("MaximumLengthValidator", e.ErrorCode); - Assert.Equal(propertyName, e.PropertyName); - }); - } - - [Fact(DisplayName = "NewId: it should create a new user ID.")] - public void NewId_it_should_create_a_new_user_Id() - { - UserId id = UserId.NewId(); - Assert.Equal(id.AggregateId.Value, id.Value); - } - - [Fact(DisplayName = "ToGuid: it should convert the identifier to a Guid.")] - public void ToGuid_it_should_convert_the_identifier_to_a_Guid() - { - Guid guid = Guid.NewGuid(); - UserId id = new(new AggregateId(guid)); - Assert.Equal(guid, id.ToGuid()); - } - - [Theory(DisplayName = "TryCreate: it should return an user identifier when the value is not empty.")] - [InlineData("85107c8e-0730-4dc1-99f4-4008d0ea7688")] - [InlineData(" admin ")] - public void TryCreate_it_should_return_an_user_identifier_when_the_value_is_not_empty(string value) - { - UserId? userId = UserId.TryCreate(value); - Assert.NotNull(userId); - Assert.Equal(value.Trim(), userId.Value); - } - - [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) - { - Assert.Null(UserId.TryCreate(value)); - } -} diff --git a/old/tests/Logitar.Identity.Tests/Categories.cs b/old/tests/Logitar.Identity.Tests/Categories.cs deleted file mode 100644 index 182f101..0000000 --- a/old/tests/Logitar.Identity.Tests/Categories.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Logitar.Identity; - -public static class Categories -{ - public const string Integration = nameof(Integration); - public const string Unit = nameof(Unit); -} diff --git a/old/tests/Logitar.Identity.Tests/PersonExtensions.cs b/old/tests/PersonExtensions.cs similarity index 100% rename from old/tests/Logitar.Identity.Tests/PersonExtensions.cs rename to old/tests/PersonExtensions.cs diff --git a/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyIdTests.cs new file mode 100644 index 0000000..85d4edc --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyIdTests.cs @@ -0,0 +1,133 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.ApiKeys; + +[Trait(Traits.Category, Categories.Unit)] +public class ApiKeyIdTests +{ + [Theory(DisplayName = "ctor: it should construct the correct ID from a stream ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_StreamId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + StreamId streamId = new(tenantId.HasValue ? string.Join(':', tenantId, entityId) : entityId.Value); + + ApiKeyId id = new(streamId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Theory(DisplayName = "ctor: it should construct the correct ID from a tenant ID and an entity ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantAndEntityId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + ApiKeyId id = new(tenantId, entityId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Fact(DisplayName = "Equals: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_Equals_Then_FalseReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1.Equals(id2)); + } + + [Theory(DisplayName = "Equals: it should return false when the object do not have the same types.")] + [InlineData(null)] + [InlineData(123)] + public void Given_DifferentTypes_When_Equals_Then_FalseReturned(object? value) + { + ApiKeyId id = ApiKeyId.NewId(); + Assert.False(id.Equals(value)); + } + + [Fact(DisplayName = "Equals: it should return true when the IDs are the same.")] + public void Given_SameIds_When_Equals_Then_TrueReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(id1.StreamId); + Assert.True(id1.Equals(id1)); + Assert.True(id1.Equals(id2)); + } + + [Fact(DisplayName = "EqualOperator: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_EqualOperator_Then_FalseReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1 == id2); + } + + [Fact(DisplayName = "EqualOperator: it should return true when the IDs are the same.")] + public void Given_SameIds_When_EqualOperator_Then_TrueReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(id1.StreamId); + Assert.True(id1 == id2); + } + + [Theory(DisplayName = "NewId: it should generate a new random ID with or without a tenant ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantId_When_NewId_Then_NewRandomIdGenerated(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + + ApiKeyId id = ApiKeyId.NewId(tenantId); + + Assert.Equal(tenantId, id.TenantId); + Assert.NotEqual(Guid.Empty, id.EntityId.ToGuid()); + } + + [Theory(DisplayName = "GetHashCode: it should return the correct hash code.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_GetHashCode_Then_CorrectHashCodeReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + ApiKeyId id = new(tenantId, entityId); + + Assert.Equal(id.Value.GetHashCode(), id.GetHashCode()); + } + + [Fact(DisplayName = "NotEqualOperator: it should return false when the IDs are the same.")] + public void Given_SameIds_When_NotEqualOperator_Then_TrueReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(id1.StreamId); + Assert.False(id1 != id2); + } + + [Fact(DisplayName = "NotEqualOperator: it should return true when the IDs are different.")] + public void Given_DifferentIds_When_NotEqualOperator_Then_TrueReturned() + { + ApiKeyId id1 = new(TenantId.NewId(), EntityId.NewId()); + ApiKeyId id2 = new(tenantId: null, id1.EntityId); + Assert.True(id1 != id2); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_ToString_Then_CorrectStringReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + ApiKeyId id = new(tenantId, entityId); + + Assert.Equal(id.Value, id.ToString()); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyTests.cs b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyTests.cs new file mode 100644 index 0000000..9322b95 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/ApiKeyTests.cs @@ -0,0 +1,303 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.ApiKeys.Events; +using Logitar.Identity.Core.Passwords; +using Logitar.Identity.Core.Roles; +using Logitar.Identity.Core.Settings; +using Logitar.Identity.Core.Users; + +namespace Logitar.Identity.Core.ApiKeys; + +[Trait(Traits.Category, Categories.Unit)] +public class ApiKeyTests +{ + private const string SecretString = "P@s$W0rD"; + + private readonly Base64Password _secret = new(SecretString); + private readonly ApiKey _apiKey; + + public ApiKeyTests() + { + _apiKey = new(new DisplayName("Test API Key"), _secret); + } + + [Fact(DisplayName = "AddRole: it should add a role.")] + public void Given_Role_When_AddRole_Then_RoleAdded() + { + Role role = new(new UniqueName(new UniqueNameSettings(), "manage_api")); + + _apiKey.AddRole(role); + Assert.Contains(role.Id, _apiKey.Roles); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyRoleAdded added && added.RoleId == role.Id); + + _apiKey.ClearChanges(); + _apiKey.AddRole(role); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + } + + [Fact(DisplayName = "AddRole: it should throw TenantMismatchException when the role is in another tenant.")] + public void Given_DifferentTenants_When_AddRole_Then_TenantMismatchException() + { + Role role = new(new UniqueName(new UniqueNameSettings(), "manage_api"), actorId: null, new RoleId(TenantId.NewId(), EntityId.NewId())); + + var exception = Assert.Throws(() => _apiKey.AddRole(role)); + Assert.Equal(_apiKey.TenantId?.Value, exception.ExpectedTenantId); + Assert.Equal(role.TenantId?.Value, exception.ActualTenantId); + } + + [Theory(DisplayName = "Authenticate: it should authenticate the API key.")] + [InlineData(null)] + [InlineData("SYSTEM")] + public void Given_CorrectSecret_When_Authenticate_Then_Authenticated(string? actorIdValue) + { + ActorId? actorId = actorIdValue == null ? null : new(actorIdValue); + + _apiKey.Authenticate(SecretString, actorId); + + ApiKeyAuthenticated? authenticated = _apiKey.Changes.Single(change => change is ApiKeyAuthenticated) as ApiKeyAuthenticated; + Assert.NotNull(authenticated); + Assert.Equal(authenticated.OccurredOn, _apiKey.AuthenticatedOn); + Assert.Equal(actorId ?? new ActorId(_apiKey.Id.Value), authenticated.ActorId); + } + + [Fact(DisplayName = "Authenticate: it should throw ApiKeyIsExpiredException when the API key is expired.")] + public void Given_Expired_When_Authenticate_Then_ApiKeyIsExpiredException() + { + _apiKey.ExpiresOn = DateTime.Now.AddMilliseconds(50); + Thread.Sleep(50); + + var exception = Assert.Throws(() => _apiKey.Authenticate(SecretString)); + Assert.Equal(_apiKey.Id.Value, exception.ApiKeyId); + } + + [Fact(DisplayName = "Authenticate: it should throw IncorrectApiKeySecretException when the secret is not correct.")] + public void Given_IncorrectSecret_When_Authenticate_Then_IncorrectApiKeySecretException() + { + string secret = "Test123!"; + var exception = Assert.Throws(() => _apiKey.Authenticate(secret)); + Assert.Equal(_apiKey.Id.Value, exception.ApiKeyId); + Assert.Equal(secret, exception.AttemptedSecret); + } + + [Theory(DisplayName = "ctor: it should construct the correct API key.")] + [InlineData(null, true)] + [InlineData("SYSTEM", false)] + public void Given_Parameters_When_ctor_Then_CorrectApiKeyConstructed(string? actorIdValue, bool generateId) + { + ActorId? actorId = actorIdValue == null ? null : new(actorIdValue); + ApiKeyId? id = generateId ? ApiKeyId.NewId() : null; + + ApiKey apiKey = new(_apiKey.DisplayName, _secret, actorId, id); + + if (id.HasValue) + { + Assert.Equal(id.Value, apiKey.Id); + } + else + { + Assert.Null(apiKey.TenantId); + Assert.NotEqual(Guid.Empty, apiKey.EntityId.ToGuid()); + } + + Assert.Equal(actorId, apiKey.CreatedBy); + Assert.Equal(_apiKey.DisplayName, apiKey.DisplayName); + + Assert.Equal(_secret, apiKey.GetType().GetField("_secret", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(apiKey)); + } + + [Fact(DisplayName = "Delete: it should delete the API key.")] + public void Given_ApiKey_When_Delete_Then_Deleted() + { + _apiKey.Delete(); + Assert.True(_apiKey.IsDeleted); + + _apiKey.ClearChanges(); + _apiKey.Delete(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + } + + [Fact(DisplayName = "Description: it should handle the updates correctly.")] + public void Given_DescriptionUpdates_When_setDescription_Then_UpdatesHandledCorrectly() + { + _apiKey.ClearChanges(); + + _apiKey.Description = null; + _apiKey.Update(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + + _apiKey.Description = new Description("This is a new API key."); + _apiKey.Update(); + Assert.True(_apiKey.HasChanges); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.Description?.Value == _apiKey.Description); + } + + [Fact(DisplayName = "DisplayName: it should handle the updates correctly.")] + public void Given_DisplayNameUpdates_When_setDisplayName_Then_UpdatesHandledCorrectly() + { + _apiKey.ClearChanges(); + + _apiKey.DisplayName = _apiKey.DisplayName; + _apiKey.Update(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + + _apiKey.DisplayName = new DisplayName("New API Key"); + _apiKey.Update(); + Assert.True(_apiKey.HasChanges); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.DisplayName == _apiKey.DisplayName); + } + + [Fact(DisplayName = "ExpiresOn: it should handle the updates correctly.")] + public void Given_ExpiresOnUpdates_When_setExpiresOn_Then_UpdatesHandledCorrectly() + { + _apiKey.ClearChanges(); + + _apiKey.ExpiresOn = _apiKey.ExpiresOn; + _apiKey.Update(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + + _apiKey.ExpiresOn = DateTime.Now.AddYears(1); + _apiKey.Update(); + Assert.True(_apiKey.HasChanges); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.ExpiresOn == _apiKey.ExpiresOn); + } + + [Fact(DisplayName = "ExpiresOn: it should throw ArgumentException when the date and time are more in the future than the current expiration.")] + public void Given_ExpirationExtended_When_setExpiresOn_Then_ArgumentException() + { + _apiKey.ExpiresOn = DateTime.Now.AddDays(90); + + var exception = Assert.Throws(() => _apiKey.ExpiresOn = DateTime.Now.AddYears(1)); + Assert.Equal("ExpiresOn", exception.ParamName); + Assert.StartsWith("The API key expiration cannot be extended.", exception.Message); + } + + [Fact(DisplayName = "ExpiresOn: it should throw ArgumentOutOfRangeException when the date and time are not set in the future.")] + public void Given_PastExpiration_When_setExpiresOn_Then_ArgumentOutOfRangeException() + { + var exception = Assert.Throws(() => _apiKey.ExpiresOn = DateTime.Now); + Assert.Equal("ExpiresOn", exception.ParamName); + Assert.StartsWith("The expiration date and time must be set in the future.", exception.Message); + } + + [Fact(DisplayName = "IsExpired: it should return false when the API is not expired.")] + public void Given_NotExpired_When_IsExpired_Then_FalseReturned() + { + Assert.False(_apiKey.IsExpired()); + + _apiKey.ExpiresOn = DateTime.Now.AddDays(90); + Assert.False(_apiKey.IsExpired()); + } + + [Fact(DisplayName = "IsExpired: it should return true when the API is expired.")] + public void Given_Expired_When_IsExpired_Then_TrueReturned() + { + _apiKey.ExpiresOn = DateTime.Now.AddDays(90); + Assert.True(_apiKey.IsExpired(DateTime.Now.AddYears(1))); + } + + [Fact(DisplayName = "It should have the correct IDs.")] + public void Given_ApiKey_When_getIds_Then_CorrectIds() + { + ApiKeyId id = new(TenantId.NewId(), EntityId.NewId()); + ApiKey apiKey = new(_apiKey.DisplayName, _secret, actorId: null, id); + Assert.Equal(id, apiKey.Id); + Assert.Equal(id.TenantId, apiKey.TenantId); + Assert.Equal(id.EntityId, apiKey.EntityId); + } + + [Fact(DisplayName = "RemoveCustomAttribute: it should remove the custom attribute.")] + public void Given_CustomAttributes_When_RemoveCustomAttribute_Then_CustomAttributeRemoved() + { + Identifier key = new("UserId"); + _apiKey.SetCustomAttribute(key, UserId.NewId().Value); + _apiKey.Update(); + + _apiKey.RemoveCustomAttribute(key); + _apiKey.Update(); + Assert.False(_apiKey.CustomAttributes.ContainsKey(key)); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.CustomAttributes[key] == null); + + _apiKey.ClearChanges(); + _apiKey.RemoveCustomAttribute(key); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + } + + [Fact(DisplayName = "RemoveRole: it should remove a role.")] + public void Given_Role_When_RemoveRole_Then_RoleRemoved() + { + Role role = new(new UniqueName(new UniqueNameSettings(), "manage_api")); + _apiKey.AddRole(role); + Assert.True(_apiKey.HasRole(role)); + + _apiKey.RemoveRole(role); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyRoleRemoved removed && removed.RoleId == role.Id); + + _apiKey.ClearChanges(); + _apiKey.RemoveRole(role); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + Assert.False(_apiKey.HasRole(role)); + } + + [Theory(DisplayName = "SetCustomAttribute: it should remove the custom attribute when the value is null, empty or white-space.")] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Given_EmptyValue_When_SetCustomAttribute_Then_CustomAttributeRemoved(string? value) + { + Identifier key = new("UserId"); + _apiKey.SetCustomAttribute(key, UserId.NewId().Value); + _apiKey.Update(); + + _apiKey.SetCustomAttribute(key, value!); + _apiKey.Update(); + Assert.False(_apiKey.CustomAttributes.ContainsKey(key)); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.CustomAttributes[key] == null); + } + + [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute.")] + public void Given_CustomAttribute_When_SetCustomAttribute_Then_CustomAttributeSet() + { + Identifier key = new("UserId"); + string value = $" {UserId.NewId().Value} "; + + _apiKey.SetCustomAttribute(key, value); + _apiKey.Update(); + Assert.Equal(_apiKey.CustomAttributes[key], value.Trim()); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.CustomAttributes[key] == value.Trim()); + + _apiKey.ClearChanges(); + _apiKey.SetCustomAttribute(key, value.Trim()); + _apiKey.Update(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + } + + [Fact(DisplayName = "ToString: it should return the correct string representation.")] + public void Given_ApiKey_When_ToString_Then_CorrectString() + { + Assert.StartsWith(_apiKey.DisplayName.Value, _apiKey.ToString()); + } + + [Theory(DisplayName = "Update: it should update the API key.")] + [InlineData(null)] + [InlineData("SYSTEM")] + public void Given_Updates_When_Update_Then_ApiKeyUpdated(string? actorIdValue) + { + ActorId? actorId = actorIdValue == null ? null : new(actorIdValue); + + _apiKey.ClearChanges(); + _apiKey.Update(); + Assert.False(_apiKey.HasChanges); + Assert.Empty(_apiKey.Changes); + + _apiKey.SetCustomAttribute(new Identifier("UserId"), UserId.NewId().Value); + _apiKey.Update(actorId); + Assert.Contains(_apiKey.Changes, change => change is ApiKeyUpdated updated && updated.ActorId == actorId && (updated.OccurredOn - DateTime.Now) < TimeSpan.FromSeconds(1)); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/ApiKeys/Events/ApiKeyUpdatedTests.cs b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/Events/ApiKeyUpdatedTests.cs new file mode 100644 index 0000000..a0b5a75 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/ApiKeys/Events/ApiKeyUpdatedTests.cs @@ -0,0 +1,28 @@ +using Logitar.Identity.Core.Users; +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Core.ApiKeys.Events; + +[Trait(Traits.Category, Categories.Unit)] +public class ApiKeyUpdatedTests +{ + private readonly JsonSerializerOptions _serializerOptions = new(); + + public ApiKeyUpdatedTests() + { + _serializerOptions.Converters.Add(new IdentifierConverter()); + } + + [Fact(DisplayName = "It should be serialized and deserialized correctly.")] + public void Given_UpdatedEvent_When_Serialize_Then_SerializedCorrectly() + { + ApiKeyUpdated updated = new(); + updated.CustomAttributes[new Identifier("UserId")] = UserId.NewId().Value; + + string json = JsonSerializer.Serialize(updated, _serializerOptions); + ApiKeyUpdated? deserialized = JsonSerializer.Deserialize(json, _serializerOptions); + + Assert.NotNull(deserialized); + Assert.Equal(updated.CustomAttributes, deserialized.CustomAttributes); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/CustomIdentifierTests.cs b/tests/Logitar.Identity.Core.UnitTests/CustomIdentifierTests.cs new file mode 100644 index 0000000..c7a3278 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/CustomIdentifierTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class CustomIdentifierTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/DescriptionTests.cs b/tests/Logitar.Identity.Core.UnitTests/DescriptionTests.cs new file mode 100644 index 0000000..e524727 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/DescriptionTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class DescriptionTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/DisplayNameTests.cs b/tests/Logitar.Identity.Core.UnitTests/DisplayNameTests.cs new file mode 100644 index 0000000..8ba2f3f --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/DisplayNameTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class DisplayNameTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/EntityIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/EntityIdTests.cs new file mode 100644 index 0000000..d9145bc --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/EntityIdTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class EntityIdTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/IdentifierTests.cs b/tests/Logitar.Identity.Core.UnitTests/IdentifierTests.cs new file mode 100644 index 0000000..fdde4d7 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/IdentifierTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class IdentifierTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/LocaleTests.cs b/tests/Logitar.Identity.Core.UnitTests/LocaleTests.cs new file mode 100644 index 0000000..4aafcb1 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/LocaleTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class LocaleTests +{ + // TODO(fpion): implement +} diff --git a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj b/tests/Logitar.Identity.Core.UnitTests/Logitar.Identity.Core.UnitTests.csproj similarity index 73% rename from old/tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj rename to tests/Logitar.Identity.Core.UnitTests/Logitar.Identity.Core.UnitTests.csproj index 58e00d9..5844fb8 100644 --- a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj +++ b/tests/Logitar.Identity.Core.UnitTests/Logitar.Identity.Core.UnitTests.csproj @@ -1,13 +1,11 @@  - net8.0 + net9.0 enable enable - false - true - Logitar.Identity.Infrastructure + Logitar.Identity.Core @@ -19,27 +17,27 @@ - - - - runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + + + diff --git a/old/tests/Logitar.Identity.Tests/Base64Password.cs b/tests/Logitar.Identity.Core.UnitTests/Passwords/Base64Password.cs similarity index 91% rename from old/tests/Logitar.Identity.Tests/Base64Password.cs rename to tests/Logitar.Identity.Core.UnitTests/Passwords/Base64Password.cs index ef383f4..0b9bea1 100644 --- a/old/tests/Logitar.Identity.Tests/Base64Password.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Passwords/Base64Password.cs @@ -1,6 +1,4 @@ -using Logitar.Identity.Domain.Passwords; - -namespace Logitar.Identity; +namespace Logitar.Identity.Core.Passwords; public record Base64Password : Password { diff --git a/tests/Logitar.Identity.Core.UnitTests/Passwords/Events/OneTimePasswordUpdatedTests.cs b/tests/Logitar.Identity.Core.UnitTests/Passwords/Events/OneTimePasswordUpdatedTests.cs new file mode 100644 index 0000000..c3e9df2 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Passwords/Events/OneTimePasswordUpdatedTests.cs @@ -0,0 +1,28 @@ +using Logitar.Identity.Core.Users; +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Core.Passwords.Events; + +[Trait(Traits.Category, Categories.Unit)] +public class OneTimePasswordUpdatedTests +{ + private readonly JsonSerializerOptions _serializerOptions = new(); + + public OneTimePasswordUpdatedTests() + { + _serializerOptions.Converters.Add(new IdentifierConverter()); + } + + [Fact(DisplayName = "It should be serialized and deserialized correctly.")] + public void Given_UpdatedEvent_When_Serialize_Then_SerializedCorrectly() + { + OneTimePasswordUpdated updated = new(); + updated.CustomAttributes[new Identifier("UserId")] = UserId.NewId().Value; + + string json = JsonSerializer.Serialize(updated, _serializerOptions); + OneTimePasswordUpdated? deserialized = JsonSerializer.Deserialize(json, _serializerOptions); + + Assert.NotNull(deserialized); + Assert.Equal(updated.CustomAttributes, deserialized.CustomAttributes); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordIdTests.cs new file mode 100644 index 0000000..a0edccf --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordIdTests.cs @@ -0,0 +1,133 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.Passwords; + +[Trait(Traits.Category, Categories.Unit)] +public class OneTimePasswordIdTests +{ + [Theory(DisplayName = "ctor: it should construct the correct ID from a stream ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_StreamId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + StreamId streamId = new(tenantId.HasValue ? string.Join(':', tenantId, entityId) : entityId.Value); + + OneTimePasswordId id = new(streamId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Theory(DisplayName = "ctor: it should construct the correct ID from a tenant ID and an entity ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantAndEntityId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + OneTimePasswordId id = new(tenantId, entityId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Fact(DisplayName = "Equals: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_Equals_Then_FalseReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1.Equals(id2)); + } + + [Theory(DisplayName = "Equals: it should return false when the object do not have the same types.")] + [InlineData(null)] + [InlineData(123)] + public void Given_DifferentTypes_When_Equals_Then_FalseReturned(object? value) + { + OneTimePasswordId id = OneTimePasswordId.NewId(); + Assert.False(id.Equals(value)); + } + + [Fact(DisplayName = "Equals: it should return true when the IDs are the same.")] + public void Given_SameIds_When_Equals_Then_TrueReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(id1.StreamId); + Assert.True(id1.Equals(id1)); + Assert.True(id1.Equals(id2)); + } + + [Fact(DisplayName = "EqualOperator: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_EqualOperator_Then_FalseReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1 == id2); + } + + [Fact(DisplayName = "EqualOperator: it should return true when the IDs are the same.")] + public void Given_SameIds_When_EqualOperator_Then_TrueReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(id1.StreamId); + Assert.True(id1 == id2); + } + + [Theory(DisplayName = "NewId: it should generate a new random ID with or without a tenant ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantId_When_NewId_Then_NewRandomIdGenerated(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + + OneTimePasswordId id = OneTimePasswordId.NewId(tenantId); + + Assert.Equal(tenantId, id.TenantId); + Assert.NotEqual(Guid.Empty, id.EntityId.ToGuid()); + } + + [Theory(DisplayName = "GetHashCode: it should return the correct hash code.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_GetHashCode_Then_CorrectHashCodeReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + OneTimePasswordId id = new(tenantId, entityId); + + Assert.Equal(id.Value.GetHashCode(), id.GetHashCode()); + } + + [Fact(DisplayName = "NotEqualOperator: it should return false when the IDs are the same.")] + public void Given_SameIds_When_NotEqualOperator_Then_TrueReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(id1.StreamId); + Assert.False(id1 != id2); + } + + [Fact(DisplayName = "NotEqualOperator: it should return true when the IDs are different.")] + public void Given_DifferentIds_When_NotEqualOperator_Then_TrueReturned() + { + OneTimePasswordId id1 = new(TenantId.NewId(), EntityId.NewId()); + OneTimePasswordId id2 = new(tenantId: null, id1.EntityId); + Assert.True(id1 != id2); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_ToString_Then_CorrectStringReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + OneTimePasswordId id = new(tenantId, entityId); + + Assert.Equal(id.Value, id.ToString()); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordTests.cs b/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordTests.cs new file mode 100644 index 0000000..f17cabd --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Passwords/OneTimePasswordTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Passwords; + +[Trait(Traits.Category, Categories.Unit)] +public class OneTimePasswordTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Roles/Events/RoleUpdatedTests.cs b/tests/Logitar.Identity.Core.UnitTests/Roles/Events/RoleUpdatedTests.cs new file mode 100644 index 0000000..92fce79 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Roles/Events/RoleUpdatedTests.cs @@ -0,0 +1,28 @@ +using Logitar.Identity.Core.Users; +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Core.Roles.Events; + +[Trait(Traits.Category, Categories.Unit)] +public class RoleUpdatedTests +{ + private readonly JsonSerializerOptions _serializerOptions = new(); + + public RoleUpdatedTests() + { + _serializerOptions.Converters.Add(new IdentifierConverter()); + } + + [Fact(DisplayName = "It should be serialized and deserialized correctly.")] + public void Given_UpdatedEvent_When_Serialize_Then_SerializedCorrectly() + { + RoleUpdated updated = new(); + updated.CustomAttributes[new Identifier("UserId")] = UserId.NewId().Value; + + string json = JsonSerializer.Serialize(updated, _serializerOptions); + RoleUpdated? deserialized = JsonSerializer.Deserialize(json, _serializerOptions); + + Assert.NotNull(deserialized); + Assert.Equal(updated.CustomAttributes, deserialized.CustomAttributes); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Roles/RoleIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleIdTests.cs new file mode 100644 index 0000000..082f9ea --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleIdTests.cs @@ -0,0 +1,133 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.Roles; + +[Trait(Traits.Category, Categories.Unit)] +public class RoleIdTests +{ + [Theory(DisplayName = "ctor: it should construct the correct ID from a stream ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_StreamId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + StreamId streamId = new(tenantId.HasValue ? string.Join(':', tenantId, entityId) : entityId.Value); + + RoleId id = new(streamId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Theory(DisplayName = "ctor: it should construct the correct ID from a tenant ID and an entity ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantAndEntityId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + RoleId id = new(tenantId, entityId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Fact(DisplayName = "Equals: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_Equals_Then_FalseReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1.Equals(id2)); + } + + [Theory(DisplayName = "Equals: it should return false when the object do not have the same types.")] + [InlineData(null)] + [InlineData(123)] + public void Given_DifferentTypes_When_Equals_Then_FalseReturned(object? value) + { + RoleId id = RoleId.NewId(); + Assert.False(id.Equals(value)); + } + + [Fact(DisplayName = "Equals: it should return true when the IDs are the same.")] + public void Given_SameIds_When_Equals_Then_TrueReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(id1.StreamId); + Assert.True(id1.Equals(id1)); + Assert.True(id1.Equals(id2)); + } + + [Fact(DisplayName = "EqualOperator: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_EqualOperator_Then_FalseReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1 == id2); + } + + [Fact(DisplayName = "EqualOperator: it should return true when the IDs are the same.")] + public void Given_SameIds_When_EqualOperator_Then_TrueReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(id1.StreamId); + Assert.True(id1 == id2); + } + + [Theory(DisplayName = "NewId: it should generate a new random ID with or without a tenant ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantId_When_NewId_Then_NewRandomIdGenerated(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + + RoleId id = RoleId.NewId(tenantId); + + Assert.Equal(tenantId, id.TenantId); + Assert.NotEqual(Guid.Empty, id.EntityId.ToGuid()); + } + + [Theory(DisplayName = "GetHashCode: it should return the correct hash code.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_GetHashCode_Then_CorrectHashCodeReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + RoleId id = new(tenantId, entityId); + + Assert.Equal(id.Value.GetHashCode(), id.GetHashCode()); + } + + [Fact(DisplayName = "NotEqualOperator: it should return false when the IDs are the same.")] + public void Given_SameIds_When_NotEqualOperator_Then_TrueReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(id1.StreamId); + Assert.False(id1 != id2); + } + + [Fact(DisplayName = "NotEqualOperator: it should return true when the IDs are different.")] + public void Given_DifferentIds_When_NotEqualOperator_Then_TrueReturned() + { + RoleId id1 = new(TenantId.NewId(), EntityId.NewId()); + RoleId id2 = new(tenantId: null, id1.EntityId); + Assert.True(id1 != id2); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_ToString_Then_CorrectStringReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + RoleId id = new(tenantId, entityId); + + Assert.Equal(id.Value, id.ToString()); + } +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleManagerTests.cs b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleManagerTests.cs similarity index 68% rename from old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleManagerTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Roles/RoleManagerTests.cs index b28aca7..9c34b6c 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Roles/RoleManagerTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleManagerTests.cs @@ -1,20 +1,21 @@ using Logitar.EventSourcing; -using Logitar.Identity.Domain.ApiKeys; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; -using Logitar.Identity.Domain.Users; +using Logitar.Identity.Core.ApiKeys; +using Logitar.Identity.Core.Passwords; +using Logitar.Identity.Core.Settings; +using Logitar.Identity.Core.Users; using Moq; -namespace Logitar.Identity.Domain.Roles; +namespace Logitar.Identity.Core.Roles; [Trait(Traits.Category, Categories.Unit)] public class RoleManagerTests { private static readonly ActorId _actorId = default; private static readonly CancellationToken _cancellationToken = default; + private readonly TenantId _tenantId = TenantId.NewId(); private readonly UniqueNameSettings _uniqueNameSettings = new(); - private readonly RoleAggregate _role; + private readonly Role _role; private readonly Mock _apiKeyRepository = new(); private readonly Mock _roleRepository = new(); @@ -23,8 +24,8 @@ public class RoleManagerTests public RoleManagerTests() { - UniqueNameUnit uniqueName = new(_uniqueNameSettings, "admin"); - _role = new(uniqueName); + UniqueName uniqueName = new(_uniqueNameSettings, "admin"); + _role = new(uniqueName, actorId: null, new RoleId(_tenantId, EntityId.NewId())); _roleManager = new(_apiKeyRepository.Object, _roleRepository.Object, _userRepository.Object); } @@ -47,30 +48,30 @@ public async Task SaveAsync_it_should_not_load_any_role_when_the_unique_name_has { _role.ClearChanges(); - _role.DisplayName = new DisplayNameUnit("Administrator"); - _role.SetCustomAttribute("manage_users", bool.TrueString); + _role.DisplayName = new DisplayName("Administrator"); + _role.SetCustomAttribute(new Identifier("manage_users"), bool.TrueString); _role.Update(); await _roleManager.SaveAsync(_role, _actorId, _cancellationToken); _roleRepository.Verify(x => x.SaveAsync(_role, _cancellationToken), Times.Once); - _roleRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _roleRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact(DisplayName = "SaveAsync: it should remove associations when it has been deleted.")] public async Task SaveAsync_it_should_remove_associations_when_it_has_been_deleted() { - RoleAggregate guest = new(new UniqueNameUnit(_uniqueNameSettings, "guest")); + Role guest = new(new UniqueName(_uniqueNameSettings, "guest"), actorId: null, new RoleId(_tenantId, EntityId.NewId())); - DisplayNameUnit displayName = new("Test"); + DisplayName displayName = new("Test"); Base64Password secret = new("S3cr3+!*"); - ApiKeyAggregate apiKey = new(displayName, secret); + ApiKey apiKey = new(displayName, secret, actorId: null, new ApiKeyId(_tenantId, EntityId.NewId())); apiKey.AddRole(_role); apiKey.AddRole(guest); _apiKeyRepository.Setup(x => x.LoadAsync(_role, _cancellationToken)).ReturnsAsync([apiKey]); - UserAggregate user = new(new UniqueNameUnit(_uniqueNameSettings, "test")); + User user = new(new UniqueName(_uniqueNameSettings, "test"), actorId: null, UserId.NewId(_tenantId)); user.AddRole(_role); user.AddRole(guest); _userRepository.Setup(x => x.LoadAsync(_role, _cancellationToken)).ReturnsAsync([user]); @@ -78,9 +79,9 @@ public async Task SaveAsync_it_should_remove_associations_when_it_has_been_delet _role.Delete(); await _roleManager.SaveAsync(_role, _actorId, _cancellationToken); - _apiKeyRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(apiKey)), _cancellationToken), Times.Once); + _apiKeyRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(apiKey)), _cancellationToken), Times.Once); _roleRepository.Verify(x => x.SaveAsync(_role, _cancellationToken), Times.Once); - _userRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(user)), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(user)), _cancellationToken), Times.Once); Assert.Equal(guest.Id, apiKey.Roles.Single()); Assert.Equal(guest.Id, user.Roles.Single()); @@ -89,13 +90,16 @@ public async Task SaveAsync_it_should_remove_associations_when_it_has_been_delet [Fact(DisplayName = "SaveAsync: it should throw UniqueNameAlreadyUsedException when an unique name conflict occurs.")] public async Task SaveAsync_it_should_throw_UniqueNameAlreadyUsedException_when_an_unique_name_conflict_occurs() { - RoleAggregate other = new(_role.UniqueName); + Role other = new(_role.UniqueName); _roleRepository.Setup(x => x.LoadAsync(_role.TenantId, _role.UniqueName, _cancellationToken)).ReturnsAsync(other); - var exception = await Assert.ThrowsAsync>( + var exception = await Assert.ThrowsAsync( async () => await _roleManager.SaveAsync(_role, _actorId, _cancellationToken) ); - Assert.Equal(_role.TenantId, exception.TenantId); - Assert.Equal(_role.UniqueName, exception.UniqueName); + Assert.Equal(typeof(Role).GetNamespaceQualifiedName(), exception.TypeName); + Assert.Equal(_role.TenantId?.Value, exception.TenantId); + Assert.Equal(other.EntityId.Value, exception.ConflictId); + Assert.Equal(_role.EntityId.Value, exception.EntityId); + Assert.Equal(_role.UniqueName.Value, exception.UniqueName); } } diff --git a/tests/Logitar.Identity.Core.UnitTests/Roles/RoleTests.cs b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleTests.cs new file mode 100644 index 0000000..fa0499e --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Roles/RoleTests.cs @@ -0,0 +1,192 @@ +using Logitar.EventSourcing; +using Logitar.Identity.Core.Roles.Events; +using Logitar.Identity.Core.Settings; + +namespace Logitar.Identity.Core.Roles; + +[Trait(Traits.Category, Categories.Unit)] +public class RoleTests +{ + private readonly Role _role; + + public RoleTests() + { + _role = new(new UniqueName(new UniqueNameSettings(), "admin")); + } + + [Theory(DisplayName = "ctor: it should construct the correct role.")] + [InlineData(null, true)] + [InlineData("SYSTEM", false)] + public void Given_Parameters_When_ctor_Then_CorrectRoleConstructed(string? actorIdValue, bool generateId) + { + ActorId? actorId = actorIdValue == null ? null : new(actorIdValue); + RoleId? id = generateId ? RoleId.NewId() : null; + + Role role = new(_role.UniqueName, actorId, id); + + if (id.HasValue) + { + Assert.Equal(id.Value, role.Id); + } + else + { + Assert.Null(role.TenantId); + Assert.NotEqual(Guid.Empty, role.EntityId.ToGuid()); + } + + Assert.Equal(actorId, role.CreatedBy); + Assert.Equal(_role.UniqueName, role.UniqueName); + } + + [Fact(DisplayName = "Delete: it should delete the role.")] + public void Given_Role_When_Delete_Then_Deleted() + { + _role.Delete(); + Assert.True(_role.IsDeleted); + + _role.ClearChanges(); + _role.Delete(); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + } + + [Fact(DisplayName = "Description: it should handle the updates correctly.")] + public void Given_DescriptionUpdates_When_setDescription_Then_UpdatesHandledCorrectly() + { + _role.ClearChanges(); + + _role.Description = null; + _role.Update(); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + + _role.Description = new Description("This is a new role."); + _role.Update(); + Assert.True(_role.HasChanges); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.Description?.Value == _role.Description); + } + + [Fact(DisplayName = "DisplayName: it should handle the updates correctly.")] + public void Given_DisplayNameUpdates_When_setDisplayName_Then_UpdatesHandledCorrectly() + { + _role.ClearChanges(); + + _role.DisplayName = _role.DisplayName; + _role.Update(); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + + _role.DisplayName = new DisplayName("New API Key"); + _role.Update(); + Assert.True(_role.HasChanges); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.DisplayName?.Value == _role.DisplayName); + } + + [Fact(DisplayName = "It should have the correct IDs.")] + public void Given_Role_When_getIds_Then_CorrectIds() + { + RoleId id = new(TenantId.NewId(), EntityId.NewId()); + Role role = new(_role.UniqueName, actorId: null, id); + Assert.Equal(id, role.Id); + Assert.Equal(id.TenantId, role.TenantId); + Assert.Equal(id.EntityId, role.EntityId); + } + + [Fact(DisplayName = "RemoveCustomAttribute: it should remove the custom attribute.")] + public void Given_CustomAttributes_When_RemoveCustomAttribute_Then_CustomAttributeRemoved() + { + Identifier key = new("CanManageApi"); + _role.SetCustomAttribute(key, bool.TrueString); + _role.Update(); + + _role.RemoveCustomAttribute(key); + _role.Update(); + Assert.False(_role.CustomAttributes.ContainsKey(key)); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.CustomAttributes[key] == null); + + _role.ClearChanges(); + _role.RemoveCustomAttribute(key); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + } + + [Theory(DisplayName = "SetCustomAttribute: it should remove the custom attribute when the value is null, empty or white-space.")] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Given_EmptyValue_When_SetCustomAttribute_Then_CustomAttributeRemoved(string? value) + { + Identifier key = new("CanManageApi"); + _role.SetCustomAttribute(key, bool.TrueString); + _role.Update(); + + _role.SetCustomAttribute(key, value!); + _role.Update(); + Assert.False(_role.CustomAttributes.ContainsKey(key)); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.CustomAttributes[key] == null); + } + + [Fact(DisplayName = "SetCustomAttribute: it should set a custom attribute.")] + public void Given_CustomAttribute_When_SetCustomAttribute_Then_CustomAttributeSet() + { + Identifier key = new("CanManageApi"); + string value = $" {bool.TrueString} "; + + _role.SetCustomAttribute(key, value); + _role.Update(); + Assert.Equal(_role.CustomAttributes[key], value.Trim()); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.CustomAttributes[key] == value.Trim()); + + _role.ClearChanges(); + _role.SetCustomAttribute(key, value.Trim()); + _role.Update(); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + } + + [Fact(DisplayName = "SetUniqueName: it should handle the updated correctly.")] + public void Given_UniqueNameUpdates_When_setSetUniqueName_Then_UpdatesHandledCorrectly() + { + UniqueName uniqueName = new(new UniqueNameSettings(), "member"); + _role.SetUniqueName(uniqueName); + Assert.Contains(_role.Changes, change => change is RoleUniqueNameChanged changed && changed.UniqueName == uniqueName); + + _role.ClearChanges(); + _role.SetUniqueName(uniqueName); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("Administrator")] + public void Given_Role_When_ToString_Then_CorrectString(string? displayName) + { + if (displayName == null) + { + Assert.StartsWith(_role.UniqueName.Value, _role.ToString()); + } + else + { + _role.DisplayName = new(displayName); + Assert.StartsWith(_role.DisplayName.Value, _role.ToString()); + } + } + + [Theory(DisplayName = "Update: it should update the role.")] + [InlineData(null)] + [InlineData("SYSTEM")] + public void Given_Updates_When_Update_Then_RoleUpdated(string? actorIdValue) + { + ActorId? actorId = actorIdValue == null ? null : new(actorIdValue); + + _role.ClearChanges(); + _role.Update(); + Assert.False(_role.HasChanges); + Assert.Empty(_role.Changes); + + _role.SetCustomAttribute(new Identifier("CanManageApi"), bool.TrueString); + _role.Update(actorId); + Assert.Contains(_role.Changes, change => change is RoleUpdated updated && updated.ActorId == actorId && (updated.OccurredOn - DateTime.Now) < TimeSpan.FromSeconds(1)); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Sessions/Events/SessionUpdatedTests.cs b/tests/Logitar.Identity.Core.UnitTests/Sessions/Events/SessionUpdatedTests.cs new file mode 100644 index 0000000..c6b3f94 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Sessions/Events/SessionUpdatedTests.cs @@ -0,0 +1,28 @@ +using Logitar.Identity.Core.Users; +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Core.Sessions.Events; + +[Trait(Traits.Category, Categories.Unit)] +public class SessionUpdatedTests +{ + private readonly JsonSerializerOptions _serializerOptions = new(); + + public SessionUpdatedTests() + { + _serializerOptions.Converters.Add(new IdentifierConverter()); + } + + [Fact(DisplayName = "It should be serialized and deserialized correctly.")] + public void Given_UpdatedEvent_When_Serialize_Then_SerializedCorrectly() + { + SessionUpdated updated = new(); + updated.CustomAttributes[new Identifier("UserId")] = UserId.NewId().Value; + + string json = JsonSerializer.Serialize(updated, _serializerOptions); + SessionUpdated? deserialized = JsonSerializer.Deserialize(json, _serializerOptions); + + Assert.NotNull(deserialized); + Assert.Equal(updated.CustomAttributes, deserialized.CustomAttributes); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionIdTests.cs new file mode 100644 index 0000000..d514d58 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionIdTests.cs @@ -0,0 +1,133 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.Sessions; + +[Trait(Traits.Category, Categories.Unit)] +public class SessionIdTests +{ + [Theory(DisplayName = "ctor: it should construct the correct ID from a stream ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_StreamId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + StreamId streamId = new(tenantId.HasValue ? string.Join(':', tenantId, entityId) : entityId.Value); + + SessionId id = new(streamId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Theory(DisplayName = "ctor: it should construct the correct ID from a tenant ID and an entity ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantAndEntityId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + SessionId id = new(tenantId, entityId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Fact(DisplayName = "Equals: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_Equals_Then_FalseReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1.Equals(id2)); + } + + [Theory(DisplayName = "Equals: it should return false when the object do not have the same types.")] + [InlineData(null)] + [InlineData(123)] + public void Given_DifferentTypes_When_Equals_Then_FalseReturned(object? value) + { + SessionId id = SessionId.NewId(); + Assert.False(id.Equals(value)); + } + + [Fact(DisplayName = "Equals: it should return true when the IDs are the same.")] + public void Given_SameIds_When_Equals_Then_TrueReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(id1.StreamId); + Assert.True(id1.Equals(id1)); + Assert.True(id1.Equals(id2)); + } + + [Fact(DisplayName = "EqualOperator: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_EqualOperator_Then_FalseReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1 == id2); + } + + [Fact(DisplayName = "EqualOperator: it should return true when the IDs are the same.")] + public void Given_SameIds_When_EqualOperator_Then_TrueReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(id1.StreamId); + Assert.True(id1 == id2); + } + + [Theory(DisplayName = "NewId: it should generate a new random ID with or without a tenant ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantId_When_NewId_Then_NewRandomIdGenerated(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + + SessionId id = SessionId.NewId(tenantId); + + Assert.Equal(tenantId, id.TenantId); + Assert.NotEqual(Guid.Empty, id.EntityId.ToGuid()); + } + + [Theory(DisplayName = "GetHashCode: it should return the correct hash code.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_GetHashCode_Then_CorrectHashCodeReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + SessionId id = new(tenantId, entityId); + + Assert.Equal(id.Value.GetHashCode(), id.GetHashCode()); + } + + [Fact(DisplayName = "NotEqualOperator: it should return false when the IDs are the same.")] + public void Given_SameIds_When_NotEqualOperator_Then_TrueReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(id1.StreamId); + Assert.False(id1 != id2); + } + + [Fact(DisplayName = "NotEqualOperator: it should return true when the IDs are different.")] + public void Given_DifferentIds_When_NotEqualOperator_Then_TrueReturned() + { + SessionId id1 = new(TenantId.NewId(), EntityId.NewId()); + SessionId id2 = new(tenantId: null, id1.EntityId); + Assert.True(id1 != id2); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_ToString_Then_CorrectStringReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + EntityId entityId = EntityId.NewId(); + + SessionId id = new(tenantId, entityId); + + Assert.Equal(id.Value, id.ToString()); + } +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionTests.cs b/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionTests.cs new file mode 100644 index 0000000..995113e --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Sessions/SessionTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Sessions; + +[Trait(Traits.Category, Categories.Unit)] +public class SessionTests +{ + // TODO(fpion): implement +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverMock.cs b/tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverMock.cs similarity index 91% rename from old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverMock.cs rename to tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverMock.cs index 20a6b28..2441a4e 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverMock.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverMock.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; -namespace Logitar.Identity.Domain.Settings; +namespace Logitar.Identity.Core.Settings; internal class RoleSettingsResolverMock : RoleSettingsResolver { diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverTests.cs b/tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverTests.cs similarity index 96% rename from old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverTests.cs index f3a8f94..6023e57 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/RoleSettingsResolverTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Settings/RoleSettingsResolverTests.cs @@ -1,7 +1,7 @@ using Logitar.Identity.Contracts.Settings; using Microsoft.Extensions.Configuration; -namespace Logitar.Identity.Domain.Settings; +namespace Logitar.Identity.Core.Settings; [Trait(Traits.Category, Categories.Unit)] public class RoleSettingsResolverTests @@ -35,3 +35,4 @@ public void Resolve_it_should_resolve_the_role_settings_correctly() Assert.Equal(_settings["Identity:Role:UniqueName:AllowedCharacters"], settings.UniqueName.AllowedCharacters); } } + diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverMock.cs b/tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverMock.cs similarity index 91% rename from old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverMock.cs rename to tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverMock.cs index 59afad0..659cb05 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverMock.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverMock.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; -namespace Logitar.Identity.Domain.Settings; +namespace Logitar.Identity.Core.Settings; internal class UserSettingsResolverMock : UserSettingsResolver { diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverTests.cs b/tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverTests.cs similarity index 96% rename from old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverTests.cs index e84a847..68ce57d 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Settings/UserSettingsResolverTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Settings/UserSettingsResolverTests.cs @@ -1,7 +1,7 @@ using Logitar.Identity.Contracts.Settings; using Microsoft.Extensions.Configuration; -namespace Logitar.Identity.Domain.Settings; +namespace Logitar.Identity.Core.Settings; [Trait(Traits.Category, Categories.Unit)] public class UserSettingsResolverTests diff --git a/tests/Logitar.Identity.Core.UnitTests/TenantIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/TenantIdTests.cs new file mode 100644 index 0000000..581671a --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/TenantIdTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class TenantIdTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/TimeZoneTests.cs b/tests/Logitar.Identity.Core.UnitTests/TimeZoneTests.cs new file mode 100644 index 0000000..e74732b --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/TimeZoneTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class TimeZoneTests +{ + // TODO(fpion): implement +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreateTokenParametersTests.cs b/tests/Logitar.Identity.Core.UnitTests/Tokens/CreateTokenParametersTests.cs similarity index 97% rename from old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreateTokenParametersTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Tokens/CreateTokenParametersTests.cs index 07928aa..a084de9 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreateTokenParametersTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Tokens/CreateTokenParametersTests.cs @@ -1,4 +1,4 @@ -namespace Logitar.Identity.Domain.Tokens; +namespace Logitar.Identity.Core.Tokens; [Trait(Traits.Category, Categories.Unit)] public class CreateTokenParametersTests diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreatedTokenTests.cs b/tests/Logitar.Identity.Core.UnitTests/Tokens/CreatedTokenTests.cs similarity index 94% rename from old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreatedTokenTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Tokens/CreatedTokenTests.cs index 5264562..27b5a33 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/CreatedTokenTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Tokens/CreatedTokenTests.cs @@ -1,6 +1,6 @@ using Microsoft.IdentityModel.Tokens; -namespace Logitar.Identity.Domain.Tokens; +namespace Logitar.Identity.Core.Tokens; [Trait(Traits.Category, Categories.Unit)] public class CreatedTokenTests diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidateTokenParametersTests.cs b/tests/Logitar.Identity.Core.UnitTests/Tokens/ValidateTokenParametersTests.cs similarity index 93% rename from old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidateTokenParametersTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Tokens/ValidateTokenParametersTests.cs index 6f321a6..849bb5b 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidateTokenParametersTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Tokens/ValidateTokenParametersTests.cs @@ -1,4 +1,4 @@ -namespace Logitar.Identity.Domain.Tokens; +namespace Logitar.Identity.Core.Tokens; [Trait(Traits.Category, Categories.Unit)] public class ValidateTokenParametersTests @@ -9,7 +9,6 @@ public class ValidateTokenParametersTests [Fact(DisplayName = "ctor: it should construct the correct parameters with options.")] public void ctor_it_should_construct_the_correct_parameters_with_options() { - DateTime now = DateTime.UtcNow; ValidateTokenOptions options = new() { ValidTypes = ["ID+JWT"], diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidatedTokenTests.cs b/tests/Logitar.Identity.Core.UnitTests/Tokens/ValidatedTokenTests.cs similarity index 94% rename from old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidatedTokenTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Tokens/ValidatedTokenTests.cs index 713bf41..873fa4a 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Tokens/ValidatedTokenTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Tokens/ValidatedTokenTests.cs @@ -1,6 +1,6 @@ using Microsoft.IdentityModel.Tokens; -namespace Logitar.Identity.Domain.Tokens; +namespace Logitar.Identity.Core.Tokens; [Trait(Traits.Category, Categories.Unit)] public class ValidatedTokenTests diff --git a/tests/Logitar.Identity.Core.UnitTests/UniqueNameTests.cs b/tests/Logitar.Identity.Core.UnitTests/UniqueNameTests.cs new file mode 100644 index 0000000..0603f03 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/UniqueNameTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class UniqueNameTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/UrlTests.cs b/tests/Logitar.Identity.Core.UnitTests/UrlTests.cs new file mode 100644 index 0000000..9c107a6 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/UrlTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core; + +[Trait(Traits.Category, Categories.Unit)] +public class UrlTests +{ + // TODO(fpion): implement +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PostalAddressHelperTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/AddressHelperTests.cs similarity index 69% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/PostalAddressHelperTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/AddressHelperTests.cs index baf1745..e362b23 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PostalAddressHelperTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/AddressHelperTests.cs @@ -1,36 +1,38 @@ -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] -public class PostalAddressHelperTests +public class AddressHelperTests { + private readonly AddressHelper _addressHelper = new(); + [Fact(DisplayName = "GetCountry: it should return null when the country is not supported.")] public void GetCountry_it_should_return_null_when_the_country_is_not_supported() { - Assert.Null(PostalAddressHelper.GetCountry("QC")); + Assert.Null(_addressHelper.GetCountry("QC")); } [Fact(DisplayName = "GetCountry: it should return the country settings when it is supported.")] public void GetCountry_it_should_return_the_country_settings_when_it_is_supported() { - Assert.NotNull(PostalAddressHelper.GetCountry("CA")); + Assert.NotNull(_addressHelper.GetCountry("CA")); } [Fact(DisplayName = "IsSupported: it should return false when the country is not supported.")] public void IsSupported_it_should_return_false_when_the_country_is_not_supported() { - Assert.False(PostalAddressHelper.IsSupported("QC")); + Assert.False(_addressHelper.IsSupported("QC")); } [Fact(DisplayName = "IsSupported: it should return true when the country is supported.")] public void IsSupported_it_should_return_true_when_the_country_is_supported() { - Assert.True(PostalAddressHelper.IsSupported("CA")); + Assert.True(_addressHelper.IsSupported("CA")); } [Fact(DisplayName = "SupportedCountries: it should return the list of supported countries.")] public void SupportedCountries_it_should_return_the_list_of_supported_countries() { - string[] expected = new[] { "CA" }; - Assert.Equal(expected, PostalAddressHelper.SupportedCountries); + string[] expected = ["CA"]; + Assert.Equal(expected, _addressHelper.SupportedCountries); } } diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/AddressUnitTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/AddressTests.cs similarity index 64% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/AddressUnitTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/AddressTests.cs index 491687b..46ae48f 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/AddressUnitTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/AddressTests.cs @@ -1,18 +1,19 @@ using Bogus; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] -public class AddressUnitTests +public class AddressTests { private readonly Faker _faker = new(); + private readonly AddressHelper _addressHelper = new(); [Theory(DisplayName = "ctor: it should construct a new postal address.")] [InlineData("150 Saint-Catherine St W", "Montreal", "CA", "QC", "H2X 3Y2", false)] [InlineData(" 150 Saint-Catherine St W", " Montreal ", " CA ", " QC ", " H2X 3Y2 ", true)] public void ctor_it_should_construct_a_new_address_address(string street, string locality, string country, string? region, string? postalCode, bool isVerified) { - AddressUnit address = new(street, locality, country, region, postalCode, isVerified); + Address address = new(_addressHelper, street, locality, country, region, postalCode, isVerified); Assert.Equal(street.Trim(), address.Street); Assert.Equal(locality.Trim(), address.Locality); Assert.Equal(postalCode?.CleanTrim(), address.PostalCode); @@ -26,51 +27,51 @@ public void ctor_it_should_construct_a_new_address_address(string street, string [InlineData(" ")] public void ctor_it_should_throw_ValidationException_when_a_component_is_empty(string value) { - var exception = Assert.Throws(() => new AddressUnit(value, value, value, propertyName: "Address")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Address.Street"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Address.Locality"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Address.Country"); + var exception = Assert.Throws(() => new Address(_addressHelper, value, value, value)); + Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Street"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Locality"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Country"); } [Fact(DisplayName = "ctor: it should throw ValidationException when a component is too long.")] public void ctor_it_should_throw_ValidationException_when_a_component_is_too_long() { - string value = _faker.Random.String(AddressUnit.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); + var value = _faker.Random.String(Address.MaximumLength + 1, minChar: 'A', maxChar: 'Z'); - var exception = Assert.Throws(() => new AddressUnit(value, value, value, value, value, propertyName: "Address")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Address.Street"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Address.Locality"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Address.Country"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Address.Region"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Address.PostalCode"); + var exception = Assert.Throws(() => new Address(_addressHelper, value, value, value, value, value)); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Street"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Locality"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Country"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Region"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "PostalCode"); } [Fact(DisplayName = "ctor: it should throw ValidationException when the country is not supported.")] public void ctor_it_should_throw_ValidationException_when_the_country_is_not_supported() { - var exception = Assert.Throws(() => new AddressUnit("150 Saint-Catherine St W", "Montreal", "QC", propertyName: "Address")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "CountryValidator" && e.PropertyName == "Address.Country"); + var exception = Assert.Throws(() => new Address(_addressHelper, "150 Saint-Catherine St W", "Montreal", "QC")); + Assert.Contains(exception.Errors, e => e.ErrorCode == "CountryValidator" && e.PropertyName == "Country"); } [Fact(DisplayName = "ctor: it should throw ValidationException when the postal code does not match the regular expression.")] public void ctor_it_should_throw_ValidationException_when_the_postal_code_does_not_match_the_regular_expression() { - var exception = Assert.Throws(() => new AddressUnit("150 Saint-Catherine St W", "Montreal", "CA", "QC", "D0L7A9", propertyName: "Address")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "PostalCodeValidator" && e.PropertyName == "Address.PostalCode"); + var exception = Assert.Throws(() => new Address(_addressHelper, "150 Saint-Catherine St W", "Montreal", "CA", "QC", "D0L7A9")); + Assert.Contains(exception.Errors, e => e.ErrorCode == "PostalCodeValidator" && e.PropertyName == "PostalCode"); } [Fact(DisplayName = "ctor: it should throw ValidationException when the region is not valid.")] public void ctor_it_should_throw_ValidationException_when_the_region_is_not_valid() { - var exception = Assert.Throws(() => new AddressUnit("150 Saint-Catherine St W", "Montreal", "CA", "ZZ", "H2X 3Y2", propertyName: "Address")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "RegionValidator" && e.PropertyName == "Address.Region"); + var exception = Assert.Throws(() => new Address(_addressHelper, "150 Saint-Catherine St W", "Montreal", "CA", "ZZ", "H2X 3Y2")); + Assert.Contains(exception.Errors, e => e.ErrorCode == "RegionValidator" && e.PropertyName == "Region"); } [Fact(DisplayName = "Format: it should format a postal address.")] public void Format_it_should_format_a_postal_address() { - AddressUnit address = new(" Jean Du Pays\r\n \r\n150 Saint-Catherine St W ", " Montreal ", " CA ", " QC ", " H2X 3Y2 "); - string expected = string.Join(Environment.NewLine, ["Jean Du Pays", "150 Saint-Catherine St W", "Montreal QC H2X 3Y2", "CA"]); - Assert.Equal(expected, address.Format()); + Address address = new(_addressHelper, " Jean Du Pays\r\n \r\n150 Saint-Catherine St W ", " Montreal ", " CA ", " QC ", " H2X 3Y2 "); + var expected = string.Join(Environment.NewLine, ["Jean Du Pays", "150 Saint-Catherine St W", "Montreal QC H2X 3Y2", "CA"]); + Assert.Equal(expected, address.ToString()); } } diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/EmailUnitTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/EmailTests.cs similarity index 72% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/EmailUnitTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/EmailTests.cs index ad560cc..5daaf75 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/EmailUnitTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/EmailTests.cs @@ -1,9 +1,9 @@ using Bogus; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] -public class EmailUnitTests +public class EmailTests { private readonly Faker _faker = new(); @@ -12,7 +12,7 @@ public class EmailUnitTests [InlineData(" admin@test.com ", true)] public void ctor_it_should_construct_a_new_email_address(string address, bool isVerified) { - EmailUnit email = new(address, isVerified); + Email email = new(address, isVerified); Assert.Equal(address.Trim(), email.Address); Assert.Equal(isVerified, email.IsVerified); } @@ -22,10 +22,10 @@ public void ctor_it_should_construct_a_new_email_address(string address, bool is [InlineData(" ")] public void ctor_it_should_throw_ValidationException_when_the_address_is_empty(string address) { - var exception = Assert.Throws(() => new EmailUnit(address, propertyName: "Email")); + var exception = Assert.Throws(() => new Email(address)); Assert.All(exception.Errors, e => { - Assert.Equal("Email.Address", e.PropertyName); + Assert.Equal("Address", e.PropertyName); Assert.True(e.ErrorCode == "EmailValidator" || e.ErrorCode == "NotEmptyValidator"); }); } @@ -33,10 +33,10 @@ public void ctor_it_should_throw_ValidationException_when_the_address_is_empty(s [Fact(DisplayName = "ctor: it should throw ValidationException when the address is not valid.")] public void ctor_it_should_throw_ValidationException_when_the_address_is_not_valid() { - var exception = Assert.Throws(() => new EmailUnit("aa@@bb..cc", propertyName: "Email")); + var exception = Assert.Throws(() => new Email("aa@@bb..cc")); Assert.All(exception.Errors, e => { - Assert.Equal("Email.Address", e.PropertyName); + Assert.Equal("Address", e.PropertyName); Assert.Equal("EmailValidator", e.ErrorCode); }); } @@ -44,12 +44,12 @@ public void ctor_it_should_throw_ValidationException_when_the_address_is_not_val [Fact(DisplayName = "ctor: it should throw ValidationException when the address is too long.")] public void ctor_it_should_throw_ValidationException_when_the_address_is_too_long() { - string address = string.Concat(_faker.Random.String(EmailUnit.MaximumLength, minChar: 'a', maxChar: 'z'), '@', _faker.Internet.DomainName()); + var address = string.Concat(_faker.Random.String(Email.MaximumLength, minChar: 'a', maxChar: 'z'), '@', _faker.Internet.DomainName()); - var exception = Assert.Throws(() => new EmailUnit(address, propertyName: "Email")); + var exception = Assert.Throws(() => new Email(address)); Assert.All(exception.Errors, e => { - Assert.Equal("Email.Address", e.PropertyName); + Assert.Equal("Address", e.PropertyName); Assert.Equal("MaximumLengthValidator", e.ErrorCode); }); } diff --git a/tests/Logitar.Identity.Core.UnitTests/Users/Events/UserUpdatedTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/Events/UserUpdatedTests.cs new file mode 100644 index 0000000..4562018 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Users/Events/UserUpdatedTests.cs @@ -0,0 +1,27 @@ +using Logitar.Identity.Infrastructure.Converters; + +namespace Logitar.Identity.Core.Users.Events; + +[Trait(Traits.Category, Categories.Unit)] +public class UserUpdatedTests +{ + private readonly JsonSerializerOptions _serializerOptions = new(); + + public UserUpdatedTests() + { + _serializerOptions.Converters.Add(new IdentifierConverter()); + } + + [Fact(DisplayName = "It should be serialized and deserialized correctly.")] + public void Given_UpdatedEvent_When_Serialize_Then_SerializedCorrectly() + { + UserUpdated updated = new(); + updated.CustomAttributes[new Identifier("HealthInsuranceNumber")] = "1234567890"; + + var json = JsonSerializer.Serialize(updated, _serializerOptions); + var deserialized = JsonSerializer.Deserialize(json, _serializerOptions); + + Assert.NotNull(deserialized); + Assert.Equal(updated.CustomAttributes, deserialized.CustomAttributes); + } +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/FoundUsersTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/FoundUsersTests.cs similarity index 62% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/FoundUsersTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/FoundUsersTests.cs index f7e1ce5..733a1f3 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/FoundUsersTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/FoundUsersTests.cs @@ -1,8 +1,7 @@ using Bogus; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; +using Logitar.Identity.Core.Settings; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] public class FoundUsersTests @@ -16,15 +15,15 @@ public void All_it_should_return_all_the_users_found() FoundUsers users = new(); Assert.Empty(users.All); - UserAggregate byId = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); - UserAggregate byEmail = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.Email)); + User byId = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); + User byEmail = new(new UniqueName(_uniqueNameSettings, _faker.Person.Email)); users = new() { ById = byId, ByEmail = byEmail }; - IEnumerable all = users.All; + IEnumerable all = users.All; Assert.Equal(2, all.Count()); Assert.Contains(byId, all); Assert.Contains(byEmail, all); @@ -36,8 +35,8 @@ public void Count_it_should_return_the_count_of_users_found() FoundUsers users = new(); Assert.Equal(0, users.Count); - UserAggregate byId = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); - UserAggregate byEmail = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.Email)); + User byId = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); + User byEmail = new(new UniqueName(_uniqueNameSettings, _faker.Person.Email)); users = new() { ById = byId, @@ -49,15 +48,15 @@ public void Count_it_should_return_the_count_of_users_found() [Fact(DisplayName = "First: it should return the first user found.")] public void First_it_should_return_the_first_user_found() { - UserAggregate byUniqueName = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); - UserAggregate byEmail = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.Email)); + User byUniqueName = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); + User byEmail = new(new UniqueName(_uniqueNameSettings, _faker.Person.Email)); FoundUsers users = new() { ByUniqueName = byUniqueName, ByEmail = byEmail }; - UserAggregate first = users.First(); + var first = users.First(); Assert.Equal(byUniqueName, first); } @@ -67,15 +66,15 @@ public void FirstOrDefault_it_should_return_the_first_user_found_or_null_if_none FoundUsers users = new(); Assert.Null(users.FirstOrDefault()); - UserAggregate byUniqueName = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); - UserAggregate byEmail = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.Email)); + User byUniqueName = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); + User byEmail = new(new UniqueName(_uniqueNameSettings, _faker.Person.Email)); users = new() { ByUniqueName = byUniqueName, ByEmail = byEmail }; - UserAggregate? first = users.FirstOrDefault(); + var first = users.FirstOrDefault(); Assert.NotNull(first); Assert.Equal(byUniqueName, first); } @@ -83,13 +82,13 @@ public void FirstOrDefault_it_should_return_the_first_user_found_or_null_if_none [Fact(DisplayName = "Single: it should return the only user found.")] public void Single_it_should_return_the_only_user_found() { - UserAggregate user = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); + User user = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); FoundUsers users = new() { ById = user }; - UserAggregate single = users.Single(); + var single = users.Single(); Assert.Equal(user, single); } @@ -99,13 +98,13 @@ public void SingleOrDefault_it_should_return_the_only_user_found_or_null_if_none FoundUsers users = new(); Assert.Null(users.SingleOrDefault()); - UserAggregate byId = new(new UniqueNameUnit(_uniqueNameSettings, _faker.Person.UserName)); + User byId = new(new UniqueName(_uniqueNameSettings, _faker.Person.UserName)); users = new() { ById = byId }; - UserAggregate? single = users.SingleOrDefault(); + var single = users.SingleOrDefault(); Assert.NotNull(single); Assert.Equal(byId, single); } diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/GenderUnitTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/GenderTests.cs similarity index 79% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/GenderUnitTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/GenderTests.cs index 30bd425..6ef5e16 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/GenderUnitTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/GenderTests.cs @@ -1,9 +1,9 @@ using Bogus; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] -public class GenderUnitTests +public class GenderTests { private readonly Faker _faker = new(); @@ -12,7 +12,7 @@ public class GenderUnitTests [InlineData(" Other ")] public void ctor_it_should_create_a_new_gender(string value) { - GenderUnit gender = new(value); + Gender gender = new(value); Assert.Equal(value.Trim(), gender.Value); } @@ -21,7 +21,7 @@ public void ctor_it_should_create_a_new_gender(string value) [InlineData("fEMALE")] public void ctor_it_should_format_a_known_gender(string value) { - GenderUnit gender = new(value); + Gender gender = new(value); Assert.Equal(value.Trim().ToLower(), gender.Value); } @@ -30,12 +30,10 @@ public void ctor_it_should_format_a_known_gender(string value) [InlineData(" ")] public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) { - string propertyName = nameof(GenderUnit); - - var exception = Assert.Throws(() => new GenderUnit(value, propertyName)); + var exception = Assert.Throws(() => new Gender(value)); Assert.All(exception.Errors, e => { - Assert.Equal(propertyName, e.PropertyName); + Assert.Equal("Value", e.PropertyName); Assert.Equal("NotEmptyValidator", e.ErrorCode); }); } @@ -43,8 +41,8 @@ public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(str [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() { - string value = _faker.Random.String(GenderUnit.MaximumLength + 1, minChar: 'a', maxChar: 'z'); - var exception = Assert.Throws(() => new GenderUnit(value)); + var value = _faker.Random.String(Gender.MaximumLength + 1, minChar: 'a', maxChar: 'z'); + var exception = Assert.Throws(() => new Gender(value)); Assert.All(exception.Errors, e => Assert.Equal("MaximumLengthValidator", e.ErrorCode)); } @@ -53,7 +51,7 @@ public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long( [InlineData("Unknown")] public void IsKnown_it_should_return_false_when_the_value_is_not_a_known_gender(string value) { - Assert.False(GenderUnit.IsKnown(value)); + Assert.False(Gender.IsKnown(value)); } [Theory(DisplayName = "IsKnown: it should return true when the value is a known gender.")] @@ -61,7 +59,7 @@ public void IsKnown_it_should_return_false_when_the_value_is_not_a_known_gender( [InlineData("fEMALE")] public void IsKnown_it_should_return_true_when_the_value_is_a_known_gender(string value) { - Assert.True(GenderUnit.IsKnown(value)); + Assert.True(Gender.IsKnown(value)); } [Theory(DisplayName = "TryCreate: it should return a gender when the value is not empty.")] @@ -69,7 +67,7 @@ public void IsKnown_it_should_return_true_when_the_value_is_a_known_gender(strin [InlineData(" Other ")] public void TryCreate_it_should_return_a_gender_when_the_value_is_not_empty(string value) { - GenderUnit? gender = GenderUnit.TryCreate(value); + var gender = Gender.TryCreate(value); Assert.NotNull(gender); Assert.Equal(value.Trim(), gender.Value); } @@ -80,6 +78,6 @@ public void TryCreate_it_should_return_a_gender_when_the_value_is_not_empty(stri [InlineData(" ")] public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) { - Assert.Null(GenderUnit.TryCreate(value)); + Assert.Null(Gender.TryCreate(value)); } } diff --git a/tests/Logitar.Identity.Core.UnitTests/Users/PersonNameTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/PersonNameTests.cs new file mode 100644 index 0000000..72001e1 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Users/PersonNameTests.cs @@ -0,0 +1,97 @@ +using Bogus; + +namespace Logitar.Identity.Core.Users; + +[Trait(Traits.Category, Categories.Unit)] +public class PersonNameTests +{ + private readonly Faker _faker = new(); + + [Fact(DisplayName = "BuildFullNameString: it should return null when the list is empty.")] + public void BuildFullNameString_it_should_return_null_when_the_list_is_empty() + { + Assert.Null(PersonName.BuildFullName(Array.Empty())); + } + + [Fact(DisplayName = "BuildFullNameString: it should return null when the list only contains empty names.")] + public void BuildFullNameString_it_should_return_null_when_the_list_only_contains_empty_names() + { + Assert.Null(PersonName.BuildFullName(["", " "])); + } + + [Fact(DisplayName = "BuildFullNameString: it should build the full name of a person.")] + public void BuildFullNameString_it_should_build_the_full_name_of_a_person() + { + string[] names = [_faker.Name.FirstName(), $" {_faker.Name.FirstName()} ", _faker.Name.LastName()]; + var expected = string.Join(' ', names.Select(name => name.Trim())); + Assert.Equal(expected, PersonName.BuildFullName(names)); + } + + [Fact(DisplayName = "BuildFullNameUnit: it should return null when the list is empty.")] + public void BuildFullNameUnit_it_should_return_null_when_the_list_is_empty() + { + Assert.Null(PersonName.BuildFullName(Array.Empty())); + } + + [Fact(DisplayName = "BuildFullNameUnit: it should build the full name of a person.")] + public void BuildFullNameUnit_it_should_build_the_full_name_of_a_person() + { + string[] names = [_faker.Name.FirstName(), $" {_faker.Name.FirstName()} ", _faker.Name.LastName()]; + var expected = string.Join(' ', names.Select(name => name.Trim())); + Assert.Equal(expected, PersonName.BuildFullName(names.Select(name => new PersonName(name)).ToArray())); + } + + [Theory(DisplayName = "ctor: it should create a new person name.")] + [InlineData("PersonName")] + [InlineData(" This is a person name. ")] + public void ctor_it_should_create_a_new_person_name(string value) + { + PersonName personName = new(value); + Assert.Equal(value.Trim(), personName.Value); + } + + [Theory(DisplayName = "ctor: it should throw ValidationException when the value is empty.")] + [InlineData("")] + [InlineData(" ")] + public void ctor_it_should_throw_ValidationException_when_the_value_is_empty(string value) + { + var exception = Assert.Throws(() => new PersonName(value)); + Assert.All(exception.Errors, e => + { + Assert.Equal("Value", e.PropertyName); + Assert.Equal("NotEmptyValidator", e.ErrorCode); + }); + } + + [Fact(DisplayName = "ctor: it should throw ValidationException when the value is too long.")] + public void ctor_it_should_throw_ValidationException_when_the_value_is_too_long() + { + var value = _faker.Random.String(PersonName.MaximumLength + 1); + + var exception = Assert.Throws(() => new PersonName(value)); + Assert.All(exception.Errors, e => + { + Assert.Equal("MaximumLengthValidator", e.ErrorCode); + Assert.Equal("Value", e.PropertyName); + }); + } + + [Theory(DisplayName = "TryCreate: it should return a person name when the value is not empty.")] + [InlineData("PersonName")] + [InlineData(" This is a person name. ")] + public void TryCreate_it_should_return_a_person_name_when_the_value_is_not_empty(string value) + { + var personName = PersonName.TryCreate(value); + Assert.NotNull(personName); + Assert.Equal(value.Trim(), personName.Value); + } + + [Theory(DisplayName = "TryCreate: it should return null when the value is null or whitespace.")] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void TryCreate_it_should_return_null_when_the_value_is_null_or_whitespace(string? value) + { + Assert.Null(PersonName.TryCreate(value)); + } +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneExtensionsTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneExtensionsTests.cs similarity index 87% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneExtensionsTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/PhoneExtensionsTests.cs index a59097b..06d57c3 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneExtensionsTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneExtensionsTests.cs @@ -1,4 +1,4 @@ -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] public class PhoneExtensionsTests @@ -6,7 +6,7 @@ public class PhoneExtensionsTests [Fact(DisplayName = "FormatToE164: it should format the phone to E.164.")] public void FormatToE164_it_should_format_the_phone_to_E_164() { - PhoneUnit phone = new("+1 (234) 567-8900", "CA", "1234567"); + Phone phone = new("+1 (234) 567-8900", "CA", "1234567"); Assert.Equal("+12345678900", phone.FormatToE164()); } diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneMock.cs b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneMock.cs similarity index 66% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneMock.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/PhoneMock.cs index f239c19..7420070 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneMock.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneMock.cs @@ -1,17 +1,19 @@ using Logitar.Identity.Contracts.Users; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; internal record PhoneMock : IPhone { public string? CountryCode { get; } public string Number { get; } public string? Extension { get; } + public bool IsVerified { get; } - public PhoneMock(string number = "+15148454636", string? countryCode = "CA", string? extension = "12345") + public PhoneMock(string number = "+15148454636", string? countryCode = "CA", string? extension = "12345", bool isVerified = false) { CountryCode = countryCode; Number = number; Extension = extension; + IsVerified = isVerified; } } diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneUnitTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneTests.cs similarity index 66% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneUnitTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/PhoneTests.cs index 99000bd..05b0626 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/PhoneUnitTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/PhoneTests.cs @@ -1,9 +1,9 @@ using Bogus; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] -public class PhoneUnitTests +public class PhoneTests { private readonly Faker _faker = new(); @@ -12,7 +12,7 @@ public class PhoneUnitTests [InlineData(" (514) 845-4636 ", "CA", "12345", true)] public void ctor_it_should_construct_a_new_phone_number(string number, string? countryCode, string? extension, bool isVerified) { - PhoneUnit phone = new(number, countryCode, extension, isVerified); + Phone phone = new(number, countryCode, extension, isVerified); Assert.Equal(countryCode?.CleanTrim(), phone.CountryCode); Assert.Equal(number.Trim(), phone.Number); Assert.Equal(extension?.CleanTrim(), phone.Extension); @@ -24,27 +24,27 @@ public void ctor_it_should_construct_a_new_phone_number(string number, string? c [InlineData(" ")] public void ctor_it_should_throw_ValidationException_when_the_number_is_empty(string number) { - var exception = Assert.Throws(() => new PhoneUnit(number, propertyName: "Phone")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Phone.Number"); + var exception = Assert.Throws(() => new Phone(number)); + Assert.Contains(exception.Errors, e => e.ErrorCode == "NotEmptyValidator" && e.PropertyName == "Number"); } [Fact(DisplayName = "ctor: it should throw ValidationException when the number is not valid.")] public void ctor_it_should_throw_ValidationException_when_the_number_is_not_valid() { - var exception = Assert.Throws(() => new PhoneUnit("+15148454636+12345", "CA", "12345", propertyName: "Phone")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "PhoneValidator" && e.PropertyName == "Phone"); + var exception = Assert.Throws(() => new Phone("+15148454636+12345", "CA", "12345")); + Assert.Contains(exception.Errors, e => e.ErrorCode == "PhoneValidator" && e.PropertyName == ""); } [Fact(DisplayName = "ctor: it should throw ValidationException when a value is too long.")] public void ctor_it_should_throw_ValidationException_when_a_value_is_too_long() { - string countryCode = "CAD"; - string number = _faker.Random.String(PhoneUnit.NumberMaximumLength + 1, minChar: '0', maxChar: '9'); - string extension = _faker.Random.String(PhoneUnit.ExtensionMaximumLength + 1, minChar: '0', maxChar: '9'); + var countryCode = "CAD"; + var number = _faker.Random.String(Phone.NumberMaximumLength + 1, minChar: '0', maxChar: '9'); + var extension = _faker.Random.String(Phone.ExtensionMaximumLength + 1, minChar: '0', maxChar: '9'); - var exception = Assert.Throws(() => new PhoneUnit(number, countryCode, extension, propertyName: "Phone")); - Assert.Contains(exception.Errors, e => e.ErrorCode == "ExactLengthValidator" && e.PropertyName == "Phone.CountryCode"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Phone.Number"); - Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Phone.Extension"); + var exception = Assert.Throws(() => new Phone(number, countryCode, extension)); + Assert.Contains(exception.Errors, e => e.ErrorCode == "ExactLengthValidator" && e.PropertyName == "CountryCode"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Number"); + Assert.Contains(exception.Errors, e => e.ErrorCode == "MaximumLengthValidator" && e.PropertyName == "Extension"); } } diff --git a/tests/Logitar.Identity.Core.UnitTests/Users/UserIdTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/UserIdTests.cs new file mode 100644 index 0000000..f8722a5 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Users/UserIdTests.cs @@ -0,0 +1,133 @@ +using Logitar.EventSourcing; + +namespace Logitar.Identity.Core.Users; + +[Trait(Traits.Category, Categories.Unit)] +public class UserIdTests +{ + [Theory(DisplayName = "ctor: it should construct the correct ID from a stream ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_StreamId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + var entityId = EntityId.NewId(); + StreamId streamId = new(tenantId.HasValue ? string.Join(':', tenantId, entityId) : entityId.Value); + + UserId id = new(streamId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Theory(DisplayName = "ctor: it should construct the correct ID from a tenant ID and an entity ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantAndEntityId_When_ctor_Then_CorrectIdConstructed(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + var entityId = EntityId.NewId(); + + UserId id = new(tenantId, entityId); + + Assert.Equal(tenantId, id.TenantId); + Assert.Equal(entityId, id.EntityId); + } + + [Fact(DisplayName = "Equals: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_Equals_Then_FalseReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1.Equals(id2)); + } + + [Theory(DisplayName = "Equals: it should return false when the object do not have the same types.")] + [InlineData(null)] + [InlineData(123)] + public void Given_DifferentTypes_When_Equals_Then_FalseReturned(object? value) + { + var id = UserId.NewId(); + Assert.False(id.Equals(value)); + } + + [Fact(DisplayName = "Equals: it should return true when the IDs are the same.")] + public void Given_SameIds_When_Equals_Then_TrueReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(id1.StreamId); + Assert.True(id1.Equals(id1)); + Assert.True(id1.Equals(id2)); + } + + [Fact(DisplayName = "EqualOperator: it should return false when the IDs are different.")] + public void Given_DifferentIds_When_EqualOperator_Then_FalseReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(tenantId: null, id1.EntityId); + Assert.False(id1 == id2); + } + + [Fact(DisplayName = "EqualOperator: it should return true when the IDs are the same.")] + public void Given_SameIds_When_EqualOperator_Then_TrueReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(id1.StreamId); + Assert.True(id1 == id2); + } + + [Theory(DisplayName = "NewId: it should generate a new random ID with or without a tenant ID.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_TenantId_When_NewId_Then_NewRandomIdGenerated(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + + var id = UserId.NewId(tenantId); + + Assert.Equal(tenantId, id.TenantId); + Assert.NotEqual(Guid.Empty, id.EntityId.ToGuid()); + } + + [Theory(DisplayName = "GetHashCode: it should return the correct hash code.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_GetHashCode_Then_CorrectHashCodeReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + var entityId = EntityId.NewId(); + + UserId id = new(tenantId, entityId); + + Assert.Equal(id.Value.GetHashCode(), id.GetHashCode()); + } + + [Fact(DisplayName = "NotEqualOperator: it should return false when the IDs are the same.")] + public void Given_SameIds_When_NotEqualOperator_Then_TrueReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(id1.StreamId); + Assert.False(id1 != id2); + } + + [Fact(DisplayName = "NotEqualOperator: it should return true when the IDs are different.")] + public void Given_DifferentIds_When_NotEqualOperator_Then_TrueReturned() + { + UserId id1 = new(TenantId.NewId(), EntityId.NewId()); + UserId id2 = new(tenantId: null, id1.EntityId); + Assert.True(id1 != id2); + } + + [Theory(DisplayName = "ToString: it should return the correct string representation.")] + [InlineData(null)] + [InlineData("TenantId")] + public void Given_Id_When_ToString_Then_CorrectStringReturned(string? tenantIdValue) + { + TenantId? tenantId = tenantIdValue == null ? null : new(tenantIdValue); + var entityId = EntityId.NewId(); + + UserId id = new(tenantId, entityId); + + Assert.Equal(id.Value, id.ToString()); + } +} diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserManagerTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/UserManagerTests.cs similarity index 64% rename from old/tests/Logitar.Identity.Domain.UnitTests/Users/UserManagerTests.cs rename to tests/Logitar.Identity.Core.UnitTests/Users/UserManagerTests.cs index 294bdf1..13810d8 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Users/UserManagerTests.cs +++ b/tests/Logitar.Identity.Core.UnitTests/Users/UserManagerTests.cs @@ -1,11 +1,10 @@ using Bogus; using Logitar.EventSourcing; -using Logitar.Identity.Domain.Sessions; -using Logitar.Identity.Domain.Settings; -using Logitar.Identity.Domain.Shared; +using Logitar.Identity.Core.Sessions; +using Logitar.Identity.Core.Settings; using Moq; -namespace Logitar.Identity.Domain.Users; +namespace Logitar.Identity.Core.Users; [Trait(Traits.Category, Categories.Unit)] public class UserManagerTests @@ -14,7 +13,7 @@ public class UserManagerTests private static readonly CancellationToken _cancellationToken = default; private readonly UserSettings _userSettings = new(); - private readonly UserAggregate _user; + private readonly User _user; private readonly Faker _faker = new(); private readonly Mock _sessionRepository = new(); @@ -24,7 +23,7 @@ public class UserManagerTests public UserManagerTests() { - UniqueNameUnit uniqueName = new(_userSettings.UniqueName, "admin"); + UniqueName uniqueName = new(_userSettings.UniqueName, "admin"); _user = new(uniqueName); _userSettingsResolver.Setup(x => x.Resolve()).Returns(_userSettings); @@ -38,52 +37,52 @@ public async Task FindAsync_it_should_find_an_user_by_email_address() _userSettings.RequireUniqueEmail = true; TenantId tenantId = new("tests"); - UserAggregate user = new(new UniqueNameUnit(_userSettings.UniqueName, _faker.Person.UserName)); - user.SetEmail(new EmailUnit(_faker.Person.Email)); + User user = new(new UniqueName(_userSettings.UniqueName, _faker.Person.UserName)); + user.SetEmail(new Email(_faker.Person.Email)); Assert.NotNull(user.Email); _userRepository.Setup(x => x.LoadAsync(tenantId, user.Email, _cancellationToken)).ReturnsAsync([user]); - FoundUsers users = await _userManager.FindAsync(tenantId.Value, user.Email.Address, _cancellationToken); + var users = await _userManager.FindAsync(tenantId.Value, user.Email.Address, _cancellationToken); Assert.NotNull(users.ByEmail); Assert.Equal(user, users.ByEmail); Assert.Single(users.All); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), _cancellationToken), Times.Never); - _userRepository.Verify(x => x.LoadAsync(tenantId, It.Is(y => y.Value == user.Email.Address), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => y.TenantId == tenantId && y.EntityId.Value == user.Email.Address), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(tenantId, It.Is(y => y.Value == user.Email.Address), _cancellationToken), Times.Once); _userRepository.Verify(x => x.LoadAsync(tenantId, user.Email, _cancellationToken), Times.Once); } [Fact(DisplayName = "FindAsync: it should find an user by ID.")] public async Task FindAsync_it_should_find_an_user_by_Id() { - UserAggregate user = new(new UniqueNameUnit(_userSettings.UniqueName, _faker.Person.UserName)); + User user = new(new UniqueName(_userSettings.UniqueName, _faker.Person.UserName)); _userRepository.Setup(x => x.LoadAsync(user.Id, _cancellationToken)).ReturnsAsync(user); - FoundUsers users = await _userManager.FindAsync(tenantIdValue: null, user.Id.Value, _cancellationToken); + var users = await _userManager.FindAsync(tenantIdValue: null, user.Id.Value, _cancellationToken); Assert.NotNull(users.ById); Assert.Equal(user, users.ById); Assert.Single(users.All); _userRepository.Verify(x => x.LoadAsync(user.Id, _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == user.Id.Value), _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), _cancellationToken), Times.Never); + _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == user.Id.Value), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), _cancellationToken), Times.Never); } [Fact(DisplayName = "FindAsync: it should find an user by unique name.")] public async Task FindAsync_it_should_find_an_user_by_unique_name() { TenantId tenantId = new("tests"); - UserAggregate user = new(new UniqueNameUnit(_userSettings.UniqueName, _faker.Person.UserName), tenantId); + User user = new(new UniqueName(_userSettings.UniqueName, _faker.Person.UserName), actorId: null, UserId.NewId(tenantId)); _userRepository.Setup(x => x.LoadAsync(tenantId, user.UniqueName, _cancellationToken)).ReturnsAsync(user); - FoundUsers users = await _userManager.FindAsync(tenantId.Value, user.UniqueName.Value, _cancellationToken); + var users = await _userManager.FindAsync(tenantId.Value, user.UniqueName.Value, _cancellationToken); Assert.NotNull(users.ByUniqueName); Assert.Equal(user, users.ByUniqueName); Assert.Single(users.All); - _userRepository.Verify(x => x.LoadAsync(It.Is(y => y.Value == user.UniqueName.Value), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => y.TenantId == tenantId && y.EntityId.Value == user.UniqueName.Value), _cancellationToken), Times.Once); _userRepository.Verify(x => x.LoadAsync(tenantId, user.UniqueName, _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), _cancellationToken), Times.Never); + _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), _cancellationToken), Times.Never); } [Fact(DisplayName = "FindAsync: it should not find an user by email address when many are found.")] @@ -91,67 +90,67 @@ public async Task FindAsync_it_should_not_find_an_user_by_email_address_when_man { _userSettings.RequireUniqueEmail = true; - EmailUnit email = new(_faker.Internet.Email()); - UserAggregate user1 = new(new UniqueNameUnit(_userSettings.UniqueName, _faker.Internet.UserName())); + Email email = new(_faker.Internet.Email()); + User user1 = new(new UniqueName(_userSettings.UniqueName, _faker.Internet.UserName())); user1.SetEmail(email); - UserAggregate user2 = new(new UniqueNameUnit(_userSettings.UniqueName, _faker.Internet.UserName())); + User user2 = new(new UniqueName(_userSettings.UniqueName, _faker.Internet.UserName())); user2.SetEmail(email); _userRepository.Setup(x => x.LoadAsync(null, email, _cancellationToken)).ReturnsAsync([user1, user2]); - FoundUsers users = await _userManager.FindAsync(tenantIdValue: null, email.Address, _cancellationToken); + var users = await _userManager.FindAsync(tenantIdValue: null, email.Address, _cancellationToken); Assert.Empty(users.All); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), _cancellationToken), Times.Never); - _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == email.Address), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => !y.TenantId.HasValue && y.EntityId.Value == email.Address), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == email.Address), _cancellationToken), Times.Once); _userRepository.Verify(x => x.LoadAsync(null, email, _cancellationToken), Times.Once); } [Fact(DisplayName = "FindAsync: it should not search by email address when email addresses are not unique.")] public async Task FindAsync_it_should_not_search_by_email_address_when_email_addresses_are_not_unique() { - string emailAddress = _faker.Person.Email; - FoundUsers users = await _userManager.FindAsync(tenantIdValue: null, emailAddress, _cancellationToken); + var emailAddress = _faker.Person.Email; + var users = await _userManager.FindAsync(tenantIdValue: null, emailAddress, _cancellationToken); Assert.Empty(users.All); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), _cancellationToken), Times.Never); - _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == emailAddress), _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(null, It.IsAny(), _cancellationToken), Times.Never); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => !y.TenantId.HasValue && y.EntityId.Value == emailAddress), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == emailAddress), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(null, It.IsAny(), _cancellationToken), Times.Never); } [Fact(DisplayName = "FindAsync: it should not search by tenant id when it is not valid.")] public async Task FindAsync_it_should_not_search_by_tenant_id_when_it_is_not_valid() { - string emailAddress = _faker.Person.Email; - FoundUsers users = await _userManager.FindAsync(emailAddress, emailAddress, _cancellationToken); + var emailAddress = _faker.Person.Email; + var users = await _userManager.FindAsync(emailAddress, emailAddress, _cancellationToken); Assert.Empty(users.All); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), _cancellationToken), Times.Never); - _userRepository.Verify(x => x.LoadAsync(null, It.Is(y => y.Value == emailAddress), _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(null, It.IsAny(), _cancellationToken), Times.Never); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => y.TenantId.HasValue && y.TenantId.Value.Value == emailAddress && y.EntityId.Value == emailAddress), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.Is(y => y.Value == emailAddress), It.Is(y => y.Value == emailAddress), _cancellationToken), Times.Once); + _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), _cancellationToken), Times.Never); } [Fact(DisplayName = "SaveAsync: it should allow multiple email address when not unique.")] public async Task SaveAsync_it_should_allow_multiple_email_address_when_not_unique() { - _user.SetEmail(new EmailUnit(_faker.Person.Email)); + _user.SetEmail(new Email(_faker.Person.Email)); await _userManager.SaveAsync(_user, _actorId, _cancellationToken); _userRepository.Verify(x => x.SaveAsync(_user, _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact(DisplayName = "SaveAsync: it should delete sessions when it has been deleted.")] public async Task SaveAsync_it_should_delete_sessions_when_it_has_been_deleted() { - SessionAggregate session = new(_user); + Session session = new(_user); _sessionRepository.Setup(x => x.LoadAsync(_user, _cancellationToken)).ReturnsAsync([session]); _user.Delete(); await _userManager.SaveAsync(_user, _actorId, _cancellationToken); - _sessionRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(session)), _cancellationToken), Times.Once); + _sessionRepository.Verify(x => x.SaveAsync(It.Is>(y => y.Single().Equals(session)), _cancellationToken), Times.Once); _userRepository.Verify(x => x.SaveAsync(_user, _cancellationToken), Times.Once); Assert.True(session.IsDeleted); @@ -176,21 +175,21 @@ public async Task SaveAsync_it_should_not_load_any_user_when_the_unique_name_has _user.FirstName = new(_faker.Person.FirstName); _user.LastName = new(_faker.Person.LastName); - _user.SetCustomAttribute("HealthInsuranceNumber", "1234567890"); + _user.SetCustomAttribute(new Identifier("HealthInsuranceNumber"), "1234567890"); _user.Update(); await _userManager.SaveAsync(_user, _actorId, _cancellationToken); _userRepository.Verify(x => x.SaveAsync(_user, _cancellationToken), Times.Once); - _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + _userRepository.Verify(x => x.LoadAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact(DisplayName = "SaveAsync: it should save the user when no custom identifier conflict occurs.")] public async Task SaveAsync_it_should_save_the_user_when_no_custom_identifier_conflict_occurs() { - string identifierKey = "GoogleId"; - string identifierValue = Guid.NewGuid().ToString(); + Identifier identifierKey = new("GoogleId"); + CustomIdentifier identifierValue = new(Guid.NewGuid().ToString()); _user.SetCustomIdentifier(identifierKey, identifierValue); _userRepository.Setup(x => x.LoadAsync(_user.TenantId, identifierKey, identifierValue, _cancellationToken)).ReturnsAsync(_user); @@ -204,21 +203,24 @@ public async Task SaveAsync_it_should_save_the_user_when_no_custom_identifier_co [Fact(DisplayName = "SaveAsync: it should throw CustomIdentifierAlreadyUsedException when a custom identifier conflict occurs.")] public async Task SaveAsync_it_should_throw_CustomIdentifierAlreadyUsedException_when_a_custom_identifier_conflict_occurs() { - string identifierKey = "GoogleId"; - string identifierValue = Guid.NewGuid().ToString(); + Identifier identifierKey = new("GoogleId"); + CustomIdentifier identifierValue = new(Guid.NewGuid().ToString()); - UserAggregate other = new(new UniqueNameUnit(_userSettings.UniqueName, "other")); + User other = new(new UniqueName(_userSettings.UniqueName, "other")); other.SetCustomIdentifier(identifierKey, identifierValue); _userRepository.Setup(x => x.LoadAsync(_user.TenantId, identifierKey, identifierValue, _cancellationToken)).ReturnsAsync(other); _user.SetCustomIdentifier(identifierKey, identifierValue); - var exception = await Assert.ThrowsAsync>( + var exception = await Assert.ThrowsAsync( async () => await _userManager.SaveAsync(_user, _actorId, _cancellationToken) ); - Assert.Equal(_user.TenantId, exception.TenantId); - Assert.Equal(identifierKey, exception.Key); - Assert.Equal(identifierValue, exception.Value); + Assert.Equal(typeof(User).GetNamespaceQualifiedName(), exception.TypeName); + Assert.Equal(_user.TenantId?.Value, exception.TenantId); + Assert.Equal(other.Id.EntityId.Value, exception.ConflictId); + Assert.Equal(_user.EntityId.Value, exception.EntityId); + Assert.Equal(identifierKey.Value, exception.Key); + Assert.Equal(identifierValue.Value, exception.Value); } [Fact(DisplayName = "SaveAsync: it should throw EmailAddressAlreadyUsedException when an email address conflict occurs.")] @@ -226,9 +228,9 @@ public async Task SaveAsync_it_should_throw_EmailAddressAlreadyUsedException_whe { _userSettings.RequireUniqueEmail = true; - EmailUnit email = new(_faker.Person.Email); + Email email = new(_faker.Person.Email); - UserAggregate other = new(new UniqueNameUnit(_userSettings.UniqueName, "other")); + User other = new(new UniqueName(_userSettings.UniqueName, "other")); other.SetEmail(email); _userRepository.Setup(x => x.LoadAsync(_user.TenantId, email, _cancellationToken)).ReturnsAsync([other]); @@ -237,20 +239,23 @@ public async Task SaveAsync_it_should_throw_EmailAddressAlreadyUsedException_whe var exception = await Assert.ThrowsAsync( async () => await _userManager.SaveAsync(_user, _actorId, _cancellationToken) ); - Assert.Equal(_user.TenantId, exception.TenantId); - Assert.Equal(email, exception.Email); + Assert.Equal(_user.TenantId?.Value, exception.TenantId); + Assert.Equal(email.Address, exception.EmailAddress); } [Fact(DisplayName = "SaveAsync: it should throw UniqueNameAlreadyUsedException when an unique name conflict occurs.")] public async Task SaveAsync_it_should_throw_UniqueNameAlreadyUsedException_when_an_unique_name_conflict_occurs() { - UserAggregate other = new(_user.UniqueName); + User other = new(_user.UniqueName); _userRepository.Setup(x => x.LoadAsync(_user.TenantId, _user.UniqueName, _cancellationToken)).ReturnsAsync(other); - var exception = await Assert.ThrowsAsync>( + var exception = await Assert.ThrowsAsync( async () => await _userManager.SaveAsync(_user, _actorId, _cancellationToken) ); - Assert.Equal(_user.TenantId, exception.TenantId); - Assert.Equal(_user.UniqueName, exception.UniqueName); + Assert.Equal(typeof(User).GetNamespaceQualifiedName(), exception.TypeName); + Assert.Equal(_user.TenantId?.Value, exception.TenantId); + Assert.Equal(other.EntityId.Value, exception.ConflictId); + Assert.Equal(_user.EntityId.Value, exception.EntityId); + Assert.Equal(_user.UniqueName.Value, exception.UniqueName); } } diff --git a/tests/Logitar.Identity.Core.UnitTests/Users/UserTests.cs b/tests/Logitar.Identity.Core.UnitTests/Users/UserTests.cs new file mode 100644 index 0000000..bd108ff --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Users/UserTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Users; + +[Trait(Traits.Category, Categories.Unit)] +public class UserTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/AddressValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/AddressValidatorTests.cs new file mode 100644 index 0000000..88ab3e4 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/AddressValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class AddressValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/AllowedCharactersValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/AllowedCharactersValidatorTests.cs new file mode 100644 index 0000000..b8282a6 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/AllowedCharactersValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class AllowedCharactersValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/EmailValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/EmailValidatorTests.cs new file mode 100644 index 0000000..7dc5127 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/EmailValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class EmailValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/FutureValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/FutureValidatorTests.cs new file mode 100644 index 0000000..6ead20a --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/FutureValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class FutureValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/IdentifierValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/IdentifierValidatorTests.cs new file mode 100644 index 0000000..fcbad38 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/IdentifierValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class IdentifierValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/LocaleValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/LocaleValidatorTests.cs new file mode 100644 index 0000000..a845e1a --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/LocaleValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class LocaleValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/PastValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/PastValidatorTests.cs new file mode 100644 index 0000000..acc2b62 --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/PastValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class PastValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/PhoneValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/PhoneValidatorTests.cs new file mode 100644 index 0000000..c80304e --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/PhoneValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class PhoneValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/TimeZoneValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/TimeZoneValidatorTests.cs new file mode 100644 index 0000000..a221c6d --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/TimeZoneValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class TimeZoneValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Core.UnitTests/Validators/UrlValidatorTests.cs b/tests/Logitar.Identity.Core.UnitTests/Validators/UrlValidatorTests.cs new file mode 100644 index 0000000..25dc71e --- /dev/null +++ b/tests/Logitar.Identity.Core.UnitTests/Validators/UrlValidatorTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Core.Validators; + +[Trait(Traits.Category, Categories.Unit)] +public class UrlValidatorTests +{ + // TODO(fpion): implement +} diff --git a/tests/Logitar.Identity.Infrastructure.UnitTests/Converters/TODO.md b/tests/Logitar.Identity.Infrastructure.UnitTests/Converters/TODO.md new file mode 100644 index 0000000..8927a33 --- /dev/null +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Converters/TODO.md @@ -0,0 +1,3 @@ +# TODO + +All converter tests. diff --git a/old/tests/Logitar.Identity.Domain.UnitTests/Logitar.Identity.Domain.UnitTests.csproj b/tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj similarity index 58% rename from old/tests/Logitar.Identity.Domain.UnitTests/Logitar.Identity.Domain.UnitTests.csproj rename to tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj index 89ba26b..57ee060 100644 --- a/old/tests/Logitar.Identity.Domain.UnitTests/Logitar.Identity.Domain.UnitTests.csproj +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Logitar.Identity.Infrastructure.UnitTests.csproj @@ -1,30 +1,13 @@  - net8.0 + net9.0 enable enable - false - true - Logitar.Identity.Domain + Logitar.Identity.Infrastructure - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - True @@ -34,15 +17,24 @@ - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + - - - + diff --git a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs similarity index 87% rename from old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs rename to tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs index 736b1e1..10d63a5 100644 --- a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Base64Strategy.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Passwords; +using Logitar.Identity.Core.Passwords; namespace Logitar.Identity.Infrastructure.Passwords; diff --git a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs similarity index 92% rename from old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs rename to tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs index c69f67d..666eeb8 100644 --- a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerMock.cs @@ -1,4 +1,4 @@ -using Logitar.Identity.Domain.Settings; +using Logitar.Identity.Core.Settings; namespace Logitar.Identity.Infrastructure.Passwords; @@ -14,3 +14,4 @@ public PasswordManagerMock(IUserSettingsResolver settingsResolver, IEnumerable base.GetStrategy(key); } + diff --git a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs similarity index 95% rename from old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs rename to tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs index b50ce17..1f160a2 100644 --- a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/PasswordManagerTests.cs @@ -1,5 +1,5 @@ -using Logitar.Identity.Domain.Passwords; -using Logitar.Identity.Domain.Settings; +using Logitar.Identity.Core.Passwords; +using Logitar.Identity.Core.Settings; using Logitar.Identity.Infrastructure.Passwords.Pbkdf2; using Moq; @@ -155,7 +155,7 @@ public void Validate_it_should_succeed_when_the_password_is_strong() _passwordManager.Validate(StrongPassword); } - [Fact(DisplayName = "Validate: it should throw ValidationException when the password is too weak.")] + [Fact(Skip = "TODO(fpion): implement", DisplayName = "Validate: it should throw ValidationException when the password is too weak.")] public void Validate_it_should_throw_ValidationException_when_the_password_is_too_weak() { var exception = Assert.Throws(() => _passwordManager.Validate(WeakPassword)); @@ -180,7 +180,7 @@ public void ValidateAndCreate_it_should_throw_PasswordStrategyNotSupportedExcept Assert.Throws(() => _passwordManager.ValidateAndCreate(StrongPassword)); } - [Fact(DisplayName = "ValidateAndCreate: it should throw ValidationException when the password is too weak.")] + [Fact(Skip = "TODO(fpion): implement", DisplayName = "ValidateAndCreate: it should throw ValidationException when the password is too weak.")] public void ValidateAndCreate_it_should_throw_ValidationException_when_the_password_is_too_weak() { var exception = Assert.Throws(() => _passwordManager.ValidateAndCreate(WeakPassword)); diff --git a/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Pbkdf2/Pbkdf2PasswordTests.cs b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Pbkdf2/Pbkdf2PasswordTests.cs new file mode 100644 index 0000000..c7cadad --- /dev/null +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Passwords/Pbkdf2/Pbkdf2PasswordTests.cs @@ -0,0 +1,7 @@ +namespace Logitar.Identity.Infrastructure.Passwords.Pbkdf2; + +[Trait(Traits.Category, Categories.Unit)] +public class Pbkdf2PasswordTests +{ + // TODO(fpion): implement +} diff --git a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs b/tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs similarity index 99% rename from old/tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs rename to tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs index 9a72665..b23fe9b 100644 --- a/old/tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs +++ b/tests/Logitar.Identity.Infrastructure.UnitTests/Tokens/JsonWebTokenManagerTests.cs @@ -1,5 +1,5 @@ using Bogus; -using Logitar.Identity.Domain.Tokens; +using Logitar.Identity.Core.Tokens; using Logitar.Security.Claims; using Logitar.Security.Cryptography; using Microsoft.IdentityModel.Tokens; diff --git a/tests/Logitar.Identity.Tests/Categories.cs b/tests/Logitar.Identity.Tests/Categories.cs new file mode 100644 index 0000000..36619a7 --- /dev/null +++ b/tests/Logitar.Identity.Tests/Categories.cs @@ -0,0 +1,6 @@ +namespace Logitar.Identity; + +public static class Categories +{ + public const string Unit = "Unit"; +} diff --git a/old/tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj b/tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj similarity index 63% rename from old/tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj rename to tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj index 5306c62..75955df 100644 --- a/old/tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj +++ b/tests/Logitar.Identity.Tests/Logitar.Identity.Tests.csproj @@ -1,12 +1,10 @@  - net8.0 + net9.0 enable enable - false - true Logitar.Identity @@ -19,26 +17,20 @@ - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + diff --git a/old/tests/Logitar.Identity.Tests/Traits.cs b/tests/Logitar.Identity.Tests/Traits.cs similarity index 55% rename from old/tests/Logitar.Identity.Tests/Traits.cs rename to tests/Logitar.Identity.Tests/Traits.cs index deb626b..0715129 100644 --- a/old/tests/Logitar.Identity.Tests/Traits.cs +++ b/tests/Logitar.Identity.Tests/Traits.cs @@ -2,5 +2,5 @@ public static class Traits { - public const string Category = nameof(Category); + public const string Category = "Category"; }