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();
}
}