using SQLInsight.Collectors.Databases; using SQLInsight.Collectors.Discovery; using SQLInsight.Connectivity; using SQLInsight.Core.Configuration; using SQLInsight.Core.Data; using SQLInsight.Core.Instances; namespace SQLInsight.IntegrationTests; /// /// Database discovery, the permission check and the database selection against a real instance, as the /// least-privilege agent login (and as a login without any rights). /// [Collection(SqlServerTestGroup.Name)] [Trait("Category", "Integration")] public class DiscoveryTests(SqlServerFixture sql) : IAsyncLifetime { private readonly string _axDatabase = "AX30_" + Guid.NewGuid().ToString("N")[..6]; public Task InitializeAsync() => sql.ExecuteAsSaAsync($""" CREATE DATABASE [{_axDatabase}]; EXEC (N'USE [{_axDatabase}]; EXEC (N''CREATE SCHEMA bmssa''); CREATE TABLE bmssa.SQLDICTIONARY (TABLEID int NOT NULL, FIELDID int NOT NULL, RECID int NOT NULL); CREATE TABLE bmssa.DATAAREA (ID nvarchar(3) NOT NULL, RECID int NOT NULL); CREATE TABLE bmssa.SYSCONFIG (ID int NOT NULL, RECID int NOT NULL);'); """); public Task DisposeAsync() => sql.ExecuteAsSaAsync($"ALTER DATABASE [{_axDatabase}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{_axDatabase}];"); private static ConnectionTarget Target(string server, string login, string password) => new() { Server = server, Authentication = AuthenticationMode.SqlLogin, UserName = login, Password = password, Provider = ConnectionProvider.SqlClient, TrustServerCertificate = true, }; [Fact] public async Task Agent_login_sees_every_database_with_its_state_and_recognises_dynamics_ax() { await using var opened = await new SqlConnectionFactory().OpenAsync(Target(sql.Server, SqlServerFixture.AgentLogin, SqlServerFixture.AgentPassword)); var info = await InstanceProbe.DetectAsync(opened.Connection); var databases = (await DatabaseDiscovery.DiscoverAsync(opened.Connection, info, CancellationToken.None)).ToDictionary(d => d.Name); Assert.True(databases["master"].IsSystem); var test = databases[SqlServerFixture.TestDatabase]; Assert.Equal(("ONLINE", true, false), (test.State, test.HasAccess, test.IsDynamicsAx)); Assert.True(test.SizeMb > 0); Assert.NotNull(test.RecoveryModel); Assert.True(test.CompatibilityLevel >= 90); Assert.Equal((true, "3.0"), (databases[_axDatabase].IsDynamicsAx, databases[_axDatabase].DynamicsAxVersion)); Assert.Equal("master", (await SingleAsync(opened, "SELECT DB_NAME()"))); // the session never changed database } [Fact] public async Task Agent_login_has_every_required_right_and_a_login_without_rights_is_told_what_is_missing() { await using (var agent = await new SqlConnectionFactory().OpenAsync(Target(sql.Server, SqlServerFixture.AgentLogin, SqlServerFixture.AgentPassword))) { var info = await InstanceProbe.DetectAsync(agent.Connection); var result = await PermissionCheck.CheckAsync(agent.Connection, info, new ConnectionTestResult { Connected = true }, CancellationToken.None); Assert.Equal((SqlServerFixture.AgentLogin, false, true), (result.Login, result.IsSysadmin, result.PermissionsChecked)); Assert.Empty(result.MissingRequired); Assert.Contains(result.Permissions, p => p.Name == "ALTER TRACE" && !p.Granted && !p.Required); } var bare = "bare_" + Guid.NewGuid().ToString("N")[..8]; await sql.ExecuteAsSaAsync($"CREATE LOGIN [{bare}] WITH PASSWORD = N'Bare-Login-1!', CHECK_POLICY = OFF;"); await using var opened = await new SqlConnectionFactory().OpenAsync(Target(sql.Server, bare, "Bare-Login-1!")); var bareInfo = await InstanceProbe.DetectAsync(opened.Connection); var missing = (await PermissionCheck.CheckAsync(opened.Connection, bareInfo, new ConnectionTestResult { Connected = true }, CancellationToken.None)) .MissingRequired.Select(p => p.Name).ToList(); Assert.Contains("VIEW ANY DEFINITION", missing); Assert.Contains("CONNECT ANY DATABASE", missing); Assert.Contains("SELECT ON msdb.dbo.sysjobs", missing); // backupset is readable by public in msdb } [Fact] public async Task Database_checks_only_run_in_the_chosen_databases() { await using var session = await AgentSession.OpenAsync(sql); var selection = new DatabaseSelection(monitorNew: false, [_axDatabase], [SqlServerFixture.TestDatabase], []); var snapshot = await session.CollectAsync(new DatabaseCollector(), selection: selection); var indexes = await session.CollectAsync(new IndexCollector(), selection: selection); var names = snapshot.Databases.Select(d => d.Name).ToList(); Assert.Contains(_axDatabase, names); Assert.Contains("msdb", names); Assert.DoesNotContain(SqlServerFixture.TestDatabase, names); Assert.DoesNotContain(indexes.Databases, d => d.Name == SqlServerFixture.TestDatabase); } private static async Task SingleAsync(OpenedConnection opened, string query) { await using var command = opened.Connection.CreateCommand(); command.CommandText = query; return (string)(await command.ExecuteScalarAsync())!; } }