using EntKube.Provisioning.Domain; using EntKube.Provisioning.Infrastructure; using FluentAssertions; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace EntKube.Provisioning.Tests.Features; /// /// Integration tests for EF Core provisioning repositories. Uses a real SQLite /// database to verify that change tracking works correctly when adding child /// entities with pre-set Guid keys to tracked aggregates. /// public class EfIdentityRealmRepositoryTests : IDisposable { private readonly SqliteConnection connection; private readonly ProvisioningDbContext db; private readonly EfIdentityRealmRepository repository; public EfIdentityRealmRepositoryTests() { connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); DbContextOptions options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; db = new ProvisioningDbContext(options); db.Database.EnsureCreated(); repository = new EfIdentityRealmRepository(db); } [Fact] public async Task UpdateAsync_AddingIdentityProvider_DoesNotThrowConcurrencyException() { // Arrange — Create and persist a realm, then load it back. Guid environmentId = Guid.NewGuid(); Guid clusterId = Guid.NewGuid(); IdentityRealm realm = IdentityRealm.Create(environmentId, "test-realm", clusterId); await repository.AddAsync(realm); IdentityRealm? loaded = await repository.GetByIdAsync(realm.Id); loaded.Should().NotBeNull(); // Act — Add a new identity provider to the tracked realm and save. // Before the fix, Update() would mark the new provider as Modified // instead of Added, causing DbUpdateConcurrencyException. RealmIdentityProvider idp = RealmIdentityProvider.Create( "google", "Google", IdentityProviderType.Google); loaded!.AddIdentityProvider(idp); Func act = async () => await repository.UpdateAsync(loaded); // Assert — Should save without throwing. await act.Should().NotThrowAsync(); IdentityRealm? reloaded = await repository.GetByIdAsync(realm.Id); reloaded!.IdentityProviders.Should().HaveCount(1); reloaded.IdentityProviders[0].Alias.Should().Be("google"); } [Fact] public async Task UpdateAsync_AddingOrganization_DoesNotThrowConcurrencyException() { // Arrange — Create and persist a realm. Guid environmentId = Guid.NewGuid(); Guid clusterId = Guid.NewGuid(); IdentityRealm realm = IdentityRealm.Create(environmentId, "org-realm", clusterId); await repository.AddAsync(realm); IdentityRealm? loaded = await repository.GetByIdAsync(realm.Id); loaded.Should().NotBeNull(); // Act — Add a new organization and save. RealmOrganization org = RealmOrganization.Create("Engineering", "Dev teams"); loaded!.AddOrganization(org); Func act = async () => await repository.UpdateAsync(loaded); // Assert await act.Should().NotThrowAsync(); IdentityRealm? reloaded = await repository.GetByIdAsync(realm.Id); reloaded!.Organizations.Should().HaveCount(1); reloaded.Organizations[0].Name.Should().Be("Engineering"); } public void Dispose() { db.Dispose(); connection.Dispose(); } }