using IdentityShroud.Core.Contracts; using IdentityShroud.Core.Model; using IdentityShroud.Core.Services; using IdentityShroud.Core.Tests.Fixtures; using IdentityShroud.TestUtils.Substitutes; using Microsoft.EntityFrameworkCore; namespace IdentityShroud.Core.Tests.Services; public class ClientServiceTests : IClassFixture { private readonly DbFixture _dbFixture; private readonly IEncryptionService _encryptionService = EncryptionServiceSubstitute.CreatePassthrough(); 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" }); db.SaveChanges(); } } [Theory] [InlineData(false)] [InlineData(true)] public async Task Create(bool allowClientCredentialsFlow) { // Setup DateTime now = DateTime.UtcNow; _clock.UtcNow().Returns(now); Client val; await using (var db = _dbFixture.CreateDbContext()) { // Act ClientService sut = new(db, _encryptionService, _clock); var response = await sut.Create( _realmId, new ClientCreateRequest { ClientId = "test-client", Name = "Test Client", Description = "A test client", AllowClientCredentialsFlow = allowClientCredentialsFlow, }, 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(allowClientCredentialsFlow, val.AllowClientCredentialsFlow); 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 (allowClientCredentialsFlow) 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 = new(actContext, _encryptionService, _clock); 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 = new(actContext, _encryptionService, _clock); Client? result = await sut.FindById(_realmId, searchId, TestContext.Current.CancellationToken); // Verify if (shouldFind) Assert.NotNull(result); else Assert.Null(result); } }