using System.Text.Json; using VolcanicCore.Api.Engine; using VolcanicCore.Api.Registries; using VolcanicCore.Core; using VolcanicCore.Persistence; using VolcanicCore.Persistence.Encryption; using VolcanicCore.Persistence.Migrations; using VolcanicCore.Persistence.Repositories; using System.Security.Cryptography; namespace VolcanicCore.Tests; public class ConnectionInstanceRegistryTests { private sealed class Handle : IAsyncDisposable { public bool Disposed; public ValueTask DisposeAsync() { Disposed = true; return default; } } private sealed class FakeProvider : IConnectionProvider { public int Opens, Closes; public string Id => "fake"; public string DisplayName => "Fake"; public JsonSchema ConfigSchema => new(JsonDocument.Parse("{}").RootElement); public IReadOnlySet SecretFields => new HashSet(); public Task OpenAsync(JsonElement config, CancellationToken ct) { Opens++; return Task.FromResult(new Handle()); } public Task CloseAsync(object h) { Closes++; return ((IAsyncDisposable)h).DisposeAsync().AsTask(); } } private static (ConnectionRepository c, FakeProvider p, ConnectionInstanceRegistry r) Setup() { var path = Path.Combine(Path.GetTempPath(), $"vc-{Guid.NewGuid():N}.db"); var f = new SqliteConnectionFactory(path); using (var cn = f.Open()) new Migrator(new IMigration[] { new Migration001Initial(), new Migration002Connections(), }).Apply(cn); var key = new byte[32]; RandomNumberGenerator.Fill(key); var enc = new EncryptionService(key); var repo = new ConnectionRepository(f, enc); var provider = new FakeProvider(); var providers = new ConnectionProviderRegistry(new IConnectionProvider[] { provider }); return (repo, provider, new ConnectionInstanceRegistry(repo, providers)); } [Fact] public async Task Opens_once_and_returns_cached_handle_on_subsequent_calls() { var (c, p, r) = Setup(); var id = c.Create("fake", "n1", "{\"x\":1}"); var a = await r.GetAsync(id, CancellationToken.None); var b = await r.GetAsync(id, CancellationToken.None); Assert.Same(a, b); Assert.Equal(1, p.Opens); } [Fact] public async Task Invalidate_disposes_current_handle_and_next_get_reopens() { var (c, p, r) = Setup(); var id = c.Create("fake", "n1", "{}"); var a = await r.GetAsync(id, CancellationToken.None); await r.InvalidateAsync(id); Assert.Equal(1, p.Closes); var b = await r.GetAsync(id, CancellationToken.None); Assert.NotSame(a, b); Assert.Equal(2, p.Opens); } [Fact] public async Task DisposeAsync_closes_all_open_handles() { var (c, p, r) = Setup(); var id1 = c.Create("fake", "n1", "{}"); var id2 = c.Create("fake", "n2", "{}"); await r.GetAsync(id1, CancellationToken.None); await r.GetAsync(id2, CancellationToken.None); await r.DisposeAsync(); Assert.Equal(2, p.Closes); } }