using System.Text.RegularExpressions; using SQLInsight.Collectors.Discovery; using SQLInsight.Core.Instances; namespace SQLInsight.Collectors.Tests; /// Database discovery and the permission check: version-guarded SQL and what is asked for on each version. public sealed class DiscoveryTests { private static InstanceInfo Instance(string version, EngineEdition edition = EngineEdition.Standard) => new() { ServerName = "SQL01", Version = SqlServerVersion.Parse(version), Edition = "Standard Edition", EngineEdition = edition, }; [Theory] [InlineData(3, 10, "3.0")] [InlineData(3, 19, "4.0+")] [InlineData(3, null, "4.0+")] [InlineData(2, 10, null)] [InlineData(0, null, null)] public void Dynamics_ax_is_recognised_by_its_three_system_tables_and_the_recid_type(int tables, int? precision, string? expected) => Assert.Equal(expected, DatabaseDiscovery.DynamicsAxVersion(tables, precision)); [Fact] public void Dynamics_ax_detection_uses_three_part_names_and_catalog_views_by_version() { var modern = DatabaseDiscovery.DynamicsAxSql("AX]30", SqlServerVersion.Parse("9.0.5000.0")); var legacy = DatabaseDiscovery.DynamicsAxSql("AX30", SqlServerVersion.Parse("8.0.2039.0")); Assert.Contains("[AX]]30].sys.tables", modern, StringComparison.Ordinal); Assert.DoesNotContain("USE ", modern, StringComparison.OrdinalIgnoreCase); Assert.Contains("[AX30].dbo.sysobjects", legacy, StringComparison.Ordinal); Assert.DoesNotContain("sys.", legacy, StringComparison.Ordinal); } [Fact] public void Sql_server_2008_r2_is_checked_for_view_server_state_and_a_user_in_each_database() { var checks = PermissionCheck.ChecksFor(Instance("10.50.6000.34")).Select(c => c.Name).ToList(); Assert.Contains("VIEW SERVER STATE", checks); Assert.Contains("VIEW ANY DEFINITION", checks); Assert.DoesNotContain("CONNECT ANY DATABASE", checks); Assert.DoesNotContain("VIEW SERVER PERFORMANCE STATE", checks); Assert.Contains("SELECT ON msdb.dbo.backupset", checks); } [Fact] public void Sql_server_2022_is_checked_for_the_granular_rights_and_managed_instance_like_2019() { var sql2022 = PermissionCheck.ChecksFor(Instance("16.0.4135.4")).Select(c => c.Name).ToList(); var managed = PermissionCheck.ChecksFor(Instance("12.0.2000.8", EngineEdition.AzureSqlManagedInstance)).Select(c => c.Name).ToList(); Assert.Contains("VIEW SERVER PERFORMANCE STATE", sql2022); Assert.Contains("VIEW SERVER SECURITY STATE", sql2022); Assert.DoesNotContain("VIEW SERVER STATE", sql2022); Assert.Contains("CONNECT ANY DATABASE", sql2022); Assert.Contains("VIEW SERVER STATE", managed); Assert.Contains("CONNECT ANY DATABASE", managed); Assert.False(PermissionCheck.ChecksFor(Instance("16.0.4135.4")).Single(c => c.Name == "ALTER TRACE").Required); } /// 2008+ constructs that make a batch fail on 2005 (as in Sql2005CompatibilityTests). private static readonly Regex[] Sql2008Only = [ new(@"DECLARE\s+@\w+\s+\w+(\s*\([\d\s,]+\))?\s*=", RegexOptions.IgnoreCase), new(@"\bMERGE\s", RegexOptions.IgnoreCase), new(@"\b(CONCAT|IIF|CHOOSE|FORMAT|TRY_CONVERT|TRY_CAST|STRING_AGG|STRING_SPLIT)\s*\(", RegexOptions.IgnoreCase), new(@"\bOFFSET\s+\S+\s+ROWS\b", RegexOptions.IgnoreCase), new(@"\bVALUES\s*\([^)]*\)\s*,\s*\(", RegexOptions.IgnoreCase), ]; [Fact] public void Discovery_and_permission_sql_runs_on_sql_server_2005() { var sql2005 = Instance("9.0.5000.0"); var batches = new[] { DatabaseDiscovery.Databases2005, DatabaseDiscovery.Databases2000, DatabaseDiscovery.DynamicsAxSql("AX30", sql2005.Version), PermissionCheck.BuildSql(PermissionCheck.ChecksFor(sql2005)), PermissionCheck.DatabasesWithoutAccessSql, }; Assert.All(batches, sql => Assert.DoesNotContain(Sql2008Only, p => p.IsMatch(sql))); Assert.All(batches, sql => Assert.DoesNotContain("PXI_BATCHMONITORTABLE", sql, StringComparison.OrdinalIgnoreCase)); Assert.DoesNotContain("sys.", DatabaseDiscovery.Databases2000, StringComparison.Ordinal); } [Fact] public void Collector_context_includes_every_database_without_a_selection() { var context = new CollectorContext(null!, Instance("16.0.4135.4"), InstanceCapabilities.From(Instance("16.0.4135.4")), DateTimeOffset.UtcNow, null, CancellationToken.None); var chosen = context with { Databases = new Core.Configuration.DatabaseSelection(false, ["Sales"], [], []) }; Assert.True(context.IncludesDatabase("Anything")); Assert.True(chosen.IncludesDatabase("Sales")); Assert.True(chosen.IncludesDatabase("msdb")); Assert.False(chosen.IncludesDatabase("Anything")); } }