Skip to content

Commit

Permalink
Miscellaneous fixes. (#46)
Browse files Browse the repository at this point in the history
* Fixed event serialization.

* v11.4

* Remove custom attributes when deleting an entity.

* Remove TODO.

* Added integration tests.
  • Loading branch information
Utar94 authored Jan 30, 2024
1 parent c5ccb02 commit 6de320b
Show file tree
Hide file tree
Showing 24 changed files with 264 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ public record ApiKeyUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the API key.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the API key is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => DisplayName != null || Description != null || ExpiresOn != null || CustomAttributes.Count > 0;
}
10 changes: 5 additions & 5 deletions src/Logitar.Identity.Domain/Logitar.Identity.Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Logitar/Identity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>0.11.3.0</AssemblyVersion>
<AssemblyVersion>0.11.4.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<Version>0.11.3</Version>
<Version>0.11.4</Version>
<NeutralLanguage>en-CA</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageReleaseNotes>Created a Contracts package.</PackageReleaseNotes>
<PackageReleaseNotes>Fixed event serialization.</PackageReleaseNotes>
<PackageTags>logitar;net;framework;identity;domain</PackageTags>
<PackageProjectUrl>https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.Domain</PackageProjectUrl>
</PropertyGroup>
Expand All @@ -45,9 +45,9 @@

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.27" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.29" />
<PackageReference Include="Logitar.EventSourcing" Version="5.0.1" />
<PackageReference Include="Logitar.Security" Version="6.1.0" />
<PackageReference Include="Logitar.Security" Version="6.1.1" />
<PackageReference Include="MediatR.Contracts" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public record OneTimePasswordUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the One-Time Password (OTP).
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the One-Time Password (OTP) is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => CustomAttributes.Count > 0;
}
3 changes: 2 additions & 1 deletion src/Logitar.Identity.Domain/Roles/Events/RoleUpdatedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ public record RoleUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the role.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the role is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => DisplayName != null || Description != null || CustomAttributes.Count > 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ public record SessionUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the session.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the session is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => CustomAttributes.Count > 0;
}
3 changes: 2 additions & 1 deletion src/Logitar.Identity.Domain/Users/Events/UserUpdatedEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ public record UserUpdatedEvent : DomainEvent, INotification
/// <summary>
/// Gets or sets the custom attribute modifications of the user.
/// </summary>
public Dictionary<string, string?> CustomAttributes { get; } = [];
public Dictionary<string, string?> CustomAttributes { get; init; } = [];

/// <summary>
/// Gets a value indicating whether or not the user is being modified.
/// </summary>
[JsonIgnore]
public bool HasChanges => FirstName != null || MiddleName != null || LastName != null || FullName != null || Nickname != null
|| Birthdate != null || Gender != null || Locale != null || TimeZone != null
|| Picture != null || Profile != null || Website != null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ public CustomAttributeService(IdentityContext context)
Context = context;
}

public virtual async Task RemoveAsync(string entityType, int entityId, CancellationToken cancellationToken)
{
CustomAttributeEntity[] entities = await Context.CustomAttributes
.Where(x => x.EntityType == entityType && x.EntityId == entityId)
.ToArrayAsync(cancellationToken);
Context.CustomAttributes.RemoveRange(entities);
}

public virtual async Task SynchronizeAsync(string entityType, int entityId, Dictionary<string, string?> customAttributes, CancellationToken cancellationToken)
{
if (customAttributes.Count == 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@

public interface ICustomAttributeService
{
Task RemoveAsync(string entityType, int entityId, CancellationToken cancellationToken = default);
Task SynchronizeAsync(string entityType, int entityId, Dictionary<string, string?> customAttributes, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public virtual async Task HandleAsync(ApiKeyDeletedEvent @event, CancellationTok
Context.ApiKeys.Remove(apiKey);

await DeleteActorAsync(apiKey, cancellationToken);
await CustomAttributes.RemoveAsync(EntityType, apiKey.ApiKeyId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public virtual async Task HandleAsync(OneTimePasswordDeletedEvent @event, Cancel
{
Context.OneTimePasswords.Remove(oneTimePassword);

await CustomAttributes.RemoveAsync(EntityType, oneTimePassword.OneTimePasswordId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public virtual async Task HandleAsync(RoleDeletedEvent @event, CancellationToken
{
Context.Roles.Remove(role);

await CustomAttributes.RemoveAsync(EntityType, role.RoleId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public virtual async Task HandleAsync(SessionDeletedEvent @event, CancellationTo
if (session != null)
{
Context.Sessions.Remove(session);

await CustomAttributes.RemoveAsync(EntityType, session.SessionId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public virtual async Task HandleAsync(UserDeletedEvent @event, CancellationToken
Context.Users.Remove(user);

await DeleteActorAsync(user, cancellationToken);
await CustomAttributes.RemoveAsync(EntityType, user.UserId, cancellationToken);
await Context.SaveChangesAsync(cancellationToken);
}
}
Expand Down Expand Up @@ -90,33 +91,34 @@ public virtual async Task HandleAsync(UserEmailChangedEvent @event, Cancellation
}
}

public virtual async Task HandleAsync(UserIdentifierChangedEvent @event, CancellationToken cancellationToken)
public virtual async Task HandleAsync(UserEnabledEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.SetCustomIdentifier(@event);
user.Enable(@event);

await Context.SaveChangesAsync(cancellationToken);
}
}
public virtual async Task HandleAsync(UserIdentifierRemovedEvent @event, CancellationToken cancellationToken)

public virtual async Task HandleAsync(UserIdentifierChangedEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.RemoveCustomIdentifier(@event);
user.SetCustomIdentifier(@event);

await Context.SaveChangesAsync(cancellationToken);
}
}

public virtual async Task HandleAsync(UserEnabledEvent @event, CancellationToken cancellationToken)
public virtual async Task HandleAsync(UserIdentifierRemovedEvent @event, CancellationToken cancellationToken)
{
UserEntity? user = await TryLoadAsync(@event.AggregateId, cancellationToken);
if (user != null)
{
user.Enable(@event);
user.RemoveCustomIdentifier(@event);

await Context.SaveChangesAsync(cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/Logitar/Identity</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<AssemblyVersion>0.11.2.0</AssemblyVersion>
<AssemblyVersion>0.11.3.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<Version>0.11.2</Version>
<Version>0.11.3</Version>
<NeutralLanguage>en-CA</NeutralLanguage>
<GenerateDocumentationFile>False</GenerateDocumentationFile>
<PackageReleaseNotes>Implemented CustomAttributeService and SessionRepository.LoadActiveAsync.</PackageReleaseNotes>
<PackageReleaseNotes>Remove custom attributes when deleting an entity.</PackageReleaseNotes>
<PackageTags>logitar;net;framework;identity;entityframeworkcore;relational</PackageTags>
<PackageProjectUrl>https://github.com/Logitar/Identity/tree/main/src/Logitar.Identity.EntityFrameworkCore.Relational</PackageProjectUrl>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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<ApiKeyUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Using Include="System.IdentityModel.Tokens.Jwt" />
<Using Include="System.Reflection" />
<Using Include="System.Security.Claims" />
<Using Include="System.Text.Json" />
<Using Include="Xunit" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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<OneTimePasswordUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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<RoleUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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<SessionUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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<UserUpdatedEvent>(json);
Assert.NotNull(deserialized);
Assert.Equal(@event.CustomAttributes, deserialized.CustomAttributes);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Logitar.EventSourcing.EntityFrameworkCore.Relational;
using Logitar.Identity.Domain.Passwords;
using Logitar.Identity.Domain.Shared;
using Logitar.Identity.Domain.Users;
using Logitar.Identity.EntityFrameworkCore.Relational;
using Logitar.Identity.EntityFrameworkCore.Relational.Entities;
using Microsoft.EntityFrameworkCore;
Expand Down Expand Up @@ -108,6 +109,36 @@ public async Task LoadAsync_it_should_load_the_One_Time_Passwords_by_identifiers
Assert.Contains(oneTimePasswords, deleted.Equals);
}

[Fact(DisplayName = "SaveAsync: it should save the deleted One-Time Password.")]
public async Task SaveAsync_it_should_save_the_deleted_One_Time_Password()
{
_oneTimePassword.SetCustomAttribute("Purpose", "reset_password");
_oneTimePassword.SetCustomAttribute("UserId", UserId.NewId().Value);
_oneTimePassword.Update();
await _oneTimePasswordRepository.SaveAsync(_oneTimePassword);

OneTimePasswordEntity? entity = await IdentityContext.OneTimePasswords.AsNoTracking()
.SingleOrDefaultAsync(x => x.AggregateId == _oneTimePassword.Id.Value);
Assert.NotNull(entity);

CustomAttributeEntity[] customAttributes = await IdentityContext.CustomAttributes.AsNoTracking()
.Where(x => x.EntityType == nameof(IdentityContext.OneTimePasswords) && x.EntityId == entity.OneTimePasswordId)
.ToArrayAsync();
Assert.Equal(_oneTimePassword.CustomAttributes.Count, customAttributes.Length);
foreach (KeyValuePair<string, string> customAttribute in _oneTimePassword.CustomAttributes)
{
Assert.Contains(customAttributes, c => c.Key == customAttribute.Key && c.Value == customAttribute.Value);
}

_oneTimePassword.Delete();
await _oneTimePasswordRepository.SaveAsync(_oneTimePassword);

customAttributes = await IdentityContext.CustomAttributes.AsNoTracking()
.Where(x => x.EntityType == nameof(IdentityContext.OneTimePasswords) && x.EntityId == entity.OneTimePasswordId)
.ToArrayAsync();
Assert.Empty(customAttributes);
}

[Fact(DisplayName = "SaveAsync: it should save the specified One-Time Password.")]
public async Task SaveAsync_it_should_save_the_specified_One_Time_Password()
{
Expand Down
Loading

0 comments on commit 6de320b

Please sign in to comment.