using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using SQLInsight.Agent; using SQLInsight.Agent.Hub; using SQLInsight.Agent.Monitoring; using SQLInsight.Collectors; using SQLInsight.Connectivity; using SQLInsight.Contracts; using SQLInsight.Hub.Data; using SQLInsight.Repository; using SQLInsight.Rules; using SQLInsight.Rules.Engine; namespace SQLInsight.IntegrationTests; /// /// The whole paid path: agent monitors real SQL Server, queues its state, delivers it to the hub, hub stores it in PostgreSQL. /// [Collection(SqlServerTestGroup.Name)] [Trait("Category", "Integration")] public class HubEndToEndTests(SqlServerFixture sql, HubFixture hub) : IClassFixture, IAsyncLifetime { private readonly string _database = "SQLInsightE2E_" + Guid.NewGuid().ToString("N")[..8]; private InsightRepository _repository = null!; public async Task InitializeAsync() { await sql.ExecuteAsSaAsync($"CREATE DATABASE [{_database}];"); _repository = new InsightRepository(sql.DatabaseConnectionString(_database)); await _repository.MigrateAsync(); } public Task DisposeAsync() => sql.ExecuteAsSaAsync($"ALTER DATABASE [{_database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{_database}];"); private sealed class TestServerClients(Func create) : IHttpClientFactory { public HttpClient CreateClient(string name) => create(); } [Fact] public async Task Agent_state_reaches_the_hub_with_sensitive_evidence_redacted() { var options = Options.Create(new HubOptions { Enabled = true, Url = new Uri("http://localhost/"), License = hub.License("E2E A/S") }); options.Value.TrustedKeys[HubFixture.KeyId] = hub.PublicKey; var outbox = new HubOutbox(options, _repository); var monitor = new InstanceMonitor( new MonitoredInstance { Name = "e2e-sql01", Server = sql.Server, Authentication = AuthenticationMode.SqlLogin, UserName = SqlServerFixture.AgentLogin, Password = SqlServerFixture.AgentPassword, Provider = ConnectionProvider.SqlClient, TrustServerCertificate = true, }, BuiltInCollectors.Create(), new Dictionary(), new SqlConnectionFactory(), new RuleEngine(BuiltInRules.Create()), _repository, NullLogger.Instance, hubOutbox: outbox); var sync = new HubSyncService(options, _repository, new TestServerClients(() => hub.Factory.CreateClient()), TimeProvider.System, NullLogger.Instance); await monitor.RunCycleAsync(DateTimeOffset.UtcNow, CancellationToken.None); var sent = await sync.RunOnceAsync(CancellationToken.None); Assert.Equal(1, sent); var status = await _repository.GetHubStatusAsync(); Assert.Equal("Valid", status!.LicenseState); Assert.Equal(0, status.Pending); Assert.NotNull(status.LastSentAt); await using var scope = hub.Factory.Services.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService(); var instance = await db.Instances.Include(i => i.Customer).SingleAsync(i => i.InstanceKey == "e2e-sql01"); Assert.Equal("E2E A/S", instance.Customer!.Name); Assert.Null(instance.ServerName); // not sent by default var findings = await db.Findings.Where(f => f.InstanceId == instance.Id && f.ResolvedAt == null).ToListAsync(); Assert.Contains(findings, f => f.RuleId == "SQLI-CFG-001"); Assert.True(await db.RuleStates.CountAsync(r => r.InstanceId == instance.Id) >= BuiltInRules.Create().Count); var latest = Assert.Single(await _repository.GetLatestSubmissionsAsync()); Assert.Equal("e2e-sql01", latest.InstanceName); Assert.NotNull(latest.SentAt); } [Fact] public async Task Rejected_license_is_given_up_not_retried_forever() { var community = hub.License("Nope A/S", SQLInsight.Licensing.LicensePlan.Professional, expires: DateTimeOffset.UtcNow.AddDays(-45)); var options = Options.Create(new HubOptions { Enabled = true, Url = new Uri("http://localhost/"), License = community }); options.Value.TrustedKeys[HubFixture.KeyId] = hub.PublicKey; await _repository.EnqueueSubmissionAsync("x", Guid.NewGuid(), "{}", DateTimeOffset.UtcNow); var sync = new HubSyncService(options, _repository, new TestServerClients(() => hub.Factory.CreateClient()), TimeProvider.System, NullLogger.Instance); // Locally the agent sees the license is long expired and does not even try. Assert.Equal(0, await sync.RunOnceAsync(CancellationToken.None)); Assert.Equal("Expired", (await _repository.GetHubStatusAsync())!.LicenseState); } }