using IdentityShroud.Api; using IdentityShroud.Core.Contracts; using IdentityShroud.Core.EFCore; using IdentityShroud.Core.Model; using IdentityShroud.Core.Security; using IdentityShroud.Core.Security.Keys; using IdentityShroud.Core.Services; using IdentityShroud.Core.Tests.Fixtures; using IdentityShroud.TestUtils.Substitutes; using Microsoft.EntityFrameworkCore; namespace IdentityShroud.Core.Tests.Services; public static class RealmDekBuilder { public static RealmDek DefaultActive() => new() { Id = DekId.NewId(), Active = true, Algorithm = KeyType.AES, KeyData = new EncryptedDek(KekId.NewId(), [ 0 ]) }; } public static class ClientCreateRequestBuilder { public static ClientCreateRequest Default() => new( "test-client", "Test Client", "A test client"); } public class ClientServiceTests : IClassFixture { private readonly DbFixture _dbFixture; private readonly NullDataEncryptionService _dataEncryptionService = new(); private readonly IClock _clock = Substitute.For(); private readonly Guid _realmId = new("a1b2c3d4-0000-0000-0000-000000000001"); public ClientServiceTests(DbFixture dbFixture) { _dbFixture = dbFixture; using Db db = dbFixture.CreateDbContext(); if (!db.Database.EnsureCreated()) TruncateTables(db); EnsureRealm(db); } private void TruncateTables(Db db) { db.Database.ExecuteSqlRaw("TRUNCATE client CASCADE;"); db.Database.ExecuteSqlRaw("TRUNCATE realm CASCADE;"); } private void EnsureRealm(Db db) { if (!db.Realms.Any(r => r.Id == _realmId)) { db.Realms.Add(new() { Id = _realmId, Slug = "test-realm", Name = "Test Realm", DataEncryptionKeys = [ RealmDekBuilder.DefaultActive(), ], }); db.SaveChanges(); } } private ClientService CreateSut(Db db) => new(db, _dataEncryptionService, new ClientCreateRequestValidator(), _clock); [Theory] [InlineData(false)] [InlineData(true)] public async Task Create(bool withSecret) { // Setup DateTime now = DateTime.UtcNow; _clock.UtcNow().Returns(now); Client val; await using (var db = _dbFixture.CreateDbContext()) { // Act ClientService sut = CreateSut(db); var response = await sut.Create( _realmId, ClientCreateRequestBuilder.Default() with { Confidential = withSecret, GenerateSecret = withSecret, }, TestContext.Current.CancellationToken); // Verify val = ResultAssert.Success(response); Assert.Equal(_realmId, val.RealmId); Assert.Equal("test-client", val.ClientId); Assert.Equal("Test Client", val.Name); Assert.Equal("A test client", val.Description); Assert.Equal(withSecret, val.Confidential); Assert.Equal(now, val.CreatedAt); } await using (var db = _dbFixture.CreateDbContext()) { var dbRecord = await db.Clients .Include(e => e.Secrets) .SingleAsync(e => e.Id == val.Id, TestContext.Current.CancellationToken); if (withSecret) Assert.Single(dbRecord.Secrets); else Assert.Empty(dbRecord.Secrets); } } [Theory] [InlineData("existing-client", true)] [InlineData("missing-client", false)] public async Task GetByClientId(string clientId, bool shouldFind) { // Setup _clock.UtcNow().Returns(DateTime.UtcNow); await using (var setupContext = _dbFixture.CreateDbContext()) { setupContext.Clients.Add(new() { RealmId = _realmId, ClientId = "existing-client", CreatedAt = DateTime.UtcNow, }); await setupContext.SaveChangesAsync(TestContext.Current.CancellationToken); } await using var actContext = _dbFixture.CreateDbContext(); // Act ClientService sut = CreateSut(actContext); Client? result = await sut.GetByClientId(_realmId, clientId, TestContext.Current.CancellationToken); // Verify if (shouldFind) Assert.NotNull(result); else Assert.Null(result); } [Theory] [InlineData(true)] [InlineData(false)] public async Task FindById(bool shouldFind) { // Setup _clock.UtcNow().Returns(DateTime.UtcNow); int existingId; await using (var setupContext = _dbFixture.CreateDbContext()) { Client client = new() { RealmId = _realmId, ClientId = "find-by-id-client", CreatedAt = DateTime.UtcNow, }; setupContext.Clients.Add(client); await setupContext.SaveChangesAsync(TestContext.Current.CancellationToken); existingId = client.Id; } int searchId = shouldFind ? existingId : existingId + 9999; await using var actContext = _dbFixture.CreateDbContext(); // Act ClientService sut = CreateSut(actContext); Client? result = await sut.FindById(_realmId, searchId, TestContext.Current.CancellationToken); // Verify if (shouldFind) Assert.NotNull(result); else Assert.Null(result); } }