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