Skip to content

Commit

Permalink
Fixed ApiKeyEntity. (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
Utar94 authored Feb 8, 2024
1 parent 9acc25c commit f4c9fc6
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void Authenticate(ApiKeyAuthenticatedEvent @event)
{
Update(@event);

AuthenticatedOn = @event.OccurredOn;
AuthenticatedOn = @event.OccurredOn.ToUniversalTime();
}

public void RemoveRole(ApiKeyRoleRemovedEvent @event)
Expand Down Expand Up @@ -105,7 +105,7 @@ public void Update(ApiKeyUpdatedEvent @event)
}
if (@event.ExpiresOn.HasValue)
{
ExpiresOn = @event.ExpiresOn.Value;
ExpiresOn = @event.ExpiresOn.Value.ToUniversalTime();
}

foreach (KeyValuePair<string, string?> customAttribute in @event.CustomAttributes)
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.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>False</GenerateDocumentationFile>
<PackageReleaseNotes>Remove custom attributes when deleting an entity.</PackageReleaseNotes>
<PackageReleaseNotes>Fixed ApiKeyEntity.</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,56 @@
using Logitar.Identity.Domain.ApiKeys;
using Logitar.Identity.Domain.Roles;
using Logitar.Identity.EntityFrameworkCore.Relational;
using Logitar.Identity.EntityFrameworkCore.Relational.Entities;

namespace Logitar.Identity.EntityFrameworkCore.SqlServer;

internal static class AssertApiKeys
{
public static void AreEqual(ApiKeyAggregate? apiKey, ApiKeyEntity? entity)
{
if (apiKey == null || entity == null)
{
Assert.Null(apiKey);
Assert.Null(entity);
return;
}

Assert.Equal(apiKey.Version, entity.Version);
Assert.Equal(apiKey.CreatedBy.Value, entity.CreatedBy);
Assertions.Equal(apiKey.CreatedOn, entity.CreatedOn, TimeSpan.FromMinutes(1));
Assert.Equal(apiKey.UpdatedBy.Value, entity.UpdatedBy);
Assertions.Equal(apiKey.UpdatedOn, entity.UpdatedOn, TimeSpan.FromMinutes(1));

Assert.Equal(apiKey.TenantId?.Value, entity.TenantId);
Assert.NotEmpty(entity.SecretHash);
Assert.Equal(apiKey.DisplayName.Value, entity.DisplayName);
Assert.Equal(apiKey.Description?.Value, entity.Description);
Assert.Equal(apiKey.ExpiresOn?.ToUniversalTime(), entity.ExpiresOn);
Assert.Equal(apiKey.AuthenticatedOn?.ToUniversalTime(), entity.AuthenticatedOn);

Assert.Equal(apiKey.CustomAttributes, entity.CustomAttributes);

foreach (RoleId roleId in apiKey.Roles)
{
Assert.Contains(entity.Roles, role => role.AggregateId == roleId.Value);
}
}

public static void AreEquivalent(ApiKeyEntity? apiKey, ActorEntity? actor)
{
if (apiKey == null || actor == null)
{
Assert.Null(apiKey);
Assert.Null(actor);
return;
}

Assert.Equal(apiKey.AggregateId, actor.Id);
Assert.Equal(ActorType.ApiKey, actor.Type);
Assert.False(actor.IsDeleted);
Assert.Equal(apiKey.DisplayName, actor.DisplayName);
Assert.Null(actor.EmailAddress);
Assert.Null(actor.PictureUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
using Logitar.Data;
using Logitar.Data.SqlServer;
using Logitar.EventSourcing;
using Logitar.EventSourcing.EntityFrameworkCore.Relational;
using Logitar.Identity.Contracts.Settings;
using Logitar.Identity.Domain.ApiKeys;
using Logitar.Identity.Domain.Passwords;
using Logitar.Identity.Domain.Roles;
using Logitar.Identity.Domain.Settings;
using Logitar.Identity.Domain.Shared;
using Logitar.Identity.EntityFrameworkCore.Relational;
using Logitar.Identity.EntityFrameworkCore.Relational.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Logitar.Identity.EntityFrameworkCore.SqlServer.Repositories;

[Trait(Traits.Category, Categories.Integration)]
public class ApiKeyRepositoryTests : RepositoryTests, IAsyncLifetime
{
private readonly IApiKeyRepository _apiKeyRepository;
private readonly IPasswordManager _passwordManager;
private readonly IRoleRepository _roleRepository;
private readonly IRoleSettings _roleSettings;

private readonly Password _secret;
private readonly string _secretString;

private readonly ApiKeyAggregate _apiKey;
private readonly RoleAggregate _role;

public ApiKeyRepositoryTests() : base()
{
_apiKeyRepository = ServiceProvider.GetRequiredService<IApiKeyRepository>();
_passwordManager = ServiceProvider.GetRequiredService<IPasswordManager>();
_roleRepository = ServiceProvider.GetRequiredService<IRoleRepository>();
_roleSettings = ServiceProvider.GetRequiredService<IRoleSettingsResolver>().Resolve();

ApiKeyId apiKeyId = ApiKeyId.NewId();
ActorId actorId = new(apiKeyId.Value);
TenantId tenantId = new("tests");

_role = new(new UniqueNameUnit(_roleSettings.UniqueName, "clerk"), tenantId, actorId);

DisplayNameUnit displayName = new("Default");
_secret = _passwordManager.GenerateBase64(32, out _secretString);
_apiKey = new(displayName, _secret, tenantId, actorId, apiKeyId)
{
Description = new DescriptionUnit("This is the default API key.")
};
_apiKey.SetExpiration(DateTime.Now.AddYears(1));
_apiKey.SetCustomAttribute("TODO", "TODO");
_apiKey.Update(actorId);

_apiKey.AddRole(_role, actorId);

_apiKey.Authenticate(_secretString, actorId);
}

public async Task InitializeAsync()
{
await EventContext.Database.MigrateAsync();
await IdentityContext.Database.MigrateAsync();

TableId[] tables =
[
IdentityDb.ApiKeys.Table,
IdentityDb.Roles.Table,
IdentityDb.CustomAttributes.Table,
IdentityDb.Actors.Table,
EventDb.Events.Table
];
foreach (TableId table in tables)
{
ICommand command = SqlServerDeleteBuilder.From(table).Build();
await IdentityContext.Database.ExecuteSqlRawAsync(command.Text, command.Parameters.ToArray());
}

await _roleRepository.SaveAsync(_role);
await _apiKeyRepository.SaveAsync(_apiKey);
}

[Fact(DisplayName = "LoadAsync: it should load all the API keys.")]
public async Task LoadAsync_it_should_load_all_the_Api_keys()
{
ApiKeyAggregate deleted = new(_apiKey.DisplayName, _secret, tenantId: null);
deleted.Delete();
await _apiKeyRepository.SaveAsync(deleted);

IEnumerable<ApiKeyAggregate> apiKeys = await _apiKeyRepository.LoadAsync(includeDeleted: true);
Assert.Equal(2, apiKeys.Count());
Assert.Contains(apiKeys, _apiKey.Equals);
Assert.Contains(apiKeys, deleted.Equals);
}

[Fact(DisplayName = "LoadAsync: it should load the API key by identifier.")]
public async Task LoadAsync_it_should_load_the_Api_key_by_identifier()
{
Assert.Null(await _apiKeyRepository.LoadAsync(ApiKeyId.NewId()));

_apiKey.Delete();
long version = _apiKey.Version;

DateTime? authenticatedOn = _apiKey.AuthenticatedOn;
_apiKey.Authenticate(_secretString);
await _apiKeyRepository.SaveAsync(_apiKey);

Assert.Null(await _apiKeyRepository.LoadAsync(_apiKey.Id, version));

ApiKeyAggregate? apiKey = await _apiKeyRepository.LoadAsync(_apiKey.Id, version, includeDeleted: true);
Assert.NotNull(apiKey);
Assert.Equal(authenticatedOn, apiKey.AuthenticatedOn);
Assert.Equal(_apiKey, apiKey);
}

[Fact(DisplayName = "LoadAsync: it should load the API key by role.")]
public async Task LoadAsync_it_should_load_the_Api_key_by_role()
{
RoleAggregate admin = new(new UniqueNameUnit(_roleSettings.UniqueName, "admin"), _apiKey.TenantId);
await _roleRepository.SaveAsync(admin);

Assert.Empty(await _apiKeyRepository.LoadAsync(admin));

IEnumerable<ApiKeyAggregate> apiKeys = await _apiKeyRepository.LoadAsync(_role);
Assert.Equal(_apiKey, apiKeys.Single());
}

[Fact(DisplayName = "LoadAsync: it should load the API keys by tenant identifier.")]
public async Task LoadAsync_it_should_load_the_apiKeys_by_tenant_identifier()
{
ApiKeyAggregate apiKey = new(new DisplayNameUnit("Other API key"), _secret, tenantId: null);
ApiKeyAggregate deleted = new(new DisplayNameUnit("deleted"), _secret, _apiKey.TenantId);
deleted.Delete();
await _apiKeyRepository.SaveAsync([apiKey, deleted]);

IEnumerable<ApiKeyAggregate> apiKeys = await _apiKeyRepository.LoadAsync(_apiKey.TenantId);
Assert.Equal(_apiKey, apiKeys.Single());
}

[Fact(DisplayName = "LoadAsync: it should load the API keys by identifiers.")]
public async Task LoadAsync_it_should_load_the_Api_keys_by_identifiers()
{
ApiKeyAggregate deleted = new(new DisplayNameUnit("deleted"), _secret, tenantId: null);
deleted.Delete();
await _apiKeyRepository.SaveAsync(deleted);

IEnumerable<ApiKeyAggregate> apiKeys = await _apiKeyRepository.LoadAsync([_apiKey.Id, deleted.Id, ApiKeyId.NewId()], includeDeleted: true);
Assert.Equal(2, apiKeys.Count());
Assert.Contains(apiKeys, _apiKey.Equals);
Assert.Contains(apiKeys, deleted.Equals);
}

[Fact(DisplayName = "SaveAsync: it should save the deleted API key.")]
public async Task SaveAsync_it_should_save_the_deleted_Api_key()
{
_apiKey.SetCustomAttribute("TODO", "TODO");
_apiKey.SetCustomAttribute("TODO", "TODO");
_apiKey.Update();
await _apiKeyRepository.SaveAsync(_apiKey);

ApiKeyEntity? entity = await IdentityContext.ApiKeys.AsNoTracking()
.SingleOrDefaultAsync(x => x.AggregateId == _apiKey.Id.Value);
Assert.NotNull(entity);

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

_apiKey.Delete();
await _apiKeyRepository.SaveAsync(_apiKey);

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

[Fact(DisplayName = "SaveAsync: it should save the specified API key.")]
public async Task SaveAsync_it_should_save_the_specified_Api_key()
{
ApiKeyEntity? entity = await IdentityContext.ApiKeys.AsNoTracking()
.Include(x => x.Roles)
.SingleOrDefaultAsync(x => x.AggregateId == _apiKey.Id.Value);
Assert.NotNull(entity);
AssertApiKeys.AreEqual(_apiKey, entity);

ActorEntity? actor = await IdentityContext.Actors.AsNoTracking()
.SingleOrDefaultAsync(x => x.Id == _apiKey.Id.Value);
AssertApiKeys.AreEquivalent(entity, actor);

Dictionary<string, string> customAttributes = await IdentityContext.CustomAttributes.AsNoTracking()
.Where(x => x.EntityType == nameof(IdentityContext.ApiKeys) && x.EntityId == entity.ApiKeyId)
.ToDictionaryAsync(x => x.Key, x => x.Value);
Assert.Equal(_apiKey.CustomAttributes, customAttributes);
}

[Fact(DisplayName = "SaveAsync: it should save the specified API keys.")]
public async Task SaveAsync_it_should_save_the_specified_Api_keys()
{
ApiKeyAggregate authenticated = new(new DisplayNameUnit("authenticated"), _secret);
ApiKeyAggregate deleted = new(new DisplayNameUnit("deleted"), _secret);
await _apiKeyRepository.SaveAsync([authenticated, deleted]);

Dictionary<string, ApiKeyEntity> entities = await IdentityContext.ApiKeys.AsNoTracking().ToDictionaryAsync(x => x.AggregateId, x => x);
Assert.True(entities.ContainsKey(authenticated.Id.Value));
Assert.True(entities.ContainsKey(deleted.Id.Value));

authenticated.Authenticate(_secretString);
deleted.Delete();
await _apiKeyRepository.SaveAsync([authenticated, deleted]);

entities = await IdentityContext.ApiKeys.AsNoTracking().ToDictionaryAsync(x => x.AggregateId, x => x);
Assert.True(entities.ContainsKey(authenticated.Id.Value));
Assert.False(entities.ContainsKey(deleted.Id.Value));

AssertApiKeys.AreEqual(authenticated, entities[authenticated.Id.Value]);

ActorEntity? actor = await IdentityContext.Actors.AsNoTracking()
.SingleOrDefaultAsync(x => x.Id == deleted.Id.Value);
Assert.NotNull(actor);
Assert.True(actor.IsDeleted);
}

public Task DisposeAsync() => Task.CompletedTask;
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public class UserRepositoryTests : RepositoryTests, IAsyncLifetime

public UserRepositoryTests() : base()
{
_roleRepository = ServiceProvider.GetRequiredService<IRoleRepository>();
_passwordManager = ServiceProvider.GetRequiredService<IPasswordManager>();
_roleRepository = ServiceProvider.GetRequiredService<IRoleRepository>();
_roleSettings = ServiceProvider.GetRequiredService<IRoleSettingsResolver>().Resolve();
_userRepository = ServiceProvider.GetRequiredService<IUserRepository>();
_userSettings = ServiceProvider.GetRequiredService<IUserSettingsResolver>().Resolve();
Expand Down

0 comments on commit f4c9fc6

Please sign in to comment.