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.Databases2005Sql, DatabaseDiscovery.Databases2000Sql, 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.Databases2000Sql, 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"));
}
}