using SQLInsight.Collectors.Tests.SqlScripts; namespace SQLInsight.Collectors.Tests; /// /// The Dynamics AX part of "SQL Scripts" (1.4): every AX rule script and AX collector script shows the SQL its rule or /// collector sends, for every AX version SQLInsight has a schema for. The SQL itself is compared with the code by /// (the -- Kilde: blocks, registered in ); /// rewrite out-of-date blocks with SQLINSIGHT_UPDATE_SQL_SCRIPTS=1. /// public sealed class SqlScriptTests { private static readonly string[] AxVersions = ["3.0", "4.0", "2009", "2012"]; private static readonly string[] Detection = ["AxDatabaseDetector.DatabasesSql", "AxDatabaseDetector.BuildDetectionSql"]; private static string[] Batch => [ .. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.BatchJobs({v})"), "AxSchemaAdapter.BatchHistory(2009, første)", "AxSchemaAdapter.BatchHistory(2009, næste)", "AxSchemaAdapter.BatchHistory(2012, første)", "AxSchemaAdapter.BatchHistory(2012, næste)", ]; private static string[] NumberSequences => [.. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.NumberSequences({v})")]; /// File → the sources (keys) whose SQL it must show. internal static Dictionary Expected() { var files = new Dictionary(StringComparer.Ordinal) { ["_collectors/AxDatabaseDetector.sql"] = [.. Detection, "AxDatabaseDetector.LoginSql", .. AxVersions.Select(v => $"AxSchemaAdapter.Companies({v})")], ["_collectors/AxBatchCollector.sql"] = Batch[Detection.Length..], ["_collectors/AxSequenceCollector.sql"] = [.. AxVersions.Select(v => $"AxSchemaAdapter.NumberSequences({v})"), .. AxVersions.Select(v => $"AxSchemaAdapter.RecIds({v})")], ["SQLI-AX-001.sql"] = Batch, ["SQLI-AX-002.sql"] = Batch, ["SQLI-AX-003.sql"] = Batch, ["SQLI-AX-004.sql"] = Batch, ["SQLI-AX-005.sql"] = NumberSequences, ["SQLI-AX-006.sql"] = NumberSequences, ["SQLI-AX-007.sql"] = NumberSequences, ["SQLI-AX-008.sql"] = NumberSequences, ["SQLI-AX-009.sql"] = [.. Detection, .. AxVersions.Select(v => $"AxSchemaAdapter.RecIds({v})")], ["SQLI-AX-010.sql"] = [ .. Detection, "BlockingCollector.BuildSql2005(2012+)", "BlockingCollector.BuildSql2005(2008)", "BlockingCollector.BuildSql2005(2005)", "BlockingCollector.Sql2000", ], }; // The AX community rules run only in the monitored databases the shared detection recognises. for (var i = 1; i <= 7; i++) { files[$"COM-AX-00{i}.sql"] = ["CustomQueryCollector.DatabasesSql", .. Detection, "CustomQueryCollector.BuildRuleSql"]; } return files; } public static TheoryData Files() => [.. Expected().Keys]; [Theory] [MemberData(nameof(Files))] public void Script_holds_the_sql_sqlinsight_runs_for_every_ax_version(string file) { var path = Path.Combine(Folder, file); Assert.True(File.Exists(path), $"SQL Scripts/{file} mangler."); var keys = File.ReadAllLines(path).Where(l => l.StartsWith("-- Kilde: ", StringComparison.Ordinal)).Select(l => l["-- Kilde: ".Length..].Trim()).ToHashSet(StringComparer.Ordinal); var missing = Expected()[file].Where(k => !keys.Contains(k)).ToList(); Assert.True(missing.Count == 0, $"SQL Scripts/{file} mangler blokkene: " + string.Join(", ", missing)); Assert.All(Expected()[file], k => Assert.True(SqlScriptSources.ByKey.ContainsKey(k), $"'{k}' er ikke registreret i SqlScriptSources")); } [Theory] [MemberData(nameof(Files))] public void Header_says_what_the_reader_needs(string file) { var text = File.ReadAllText(Path.Combine(Folder, file)); // The header as running text: comment lines wrapped at 120 characters are joined again. var header = System.Text.RegularExpressions.Regex.Replace(text[..text.IndexOf("-- Variant:", StringComparison.Ordinal)], @"\r?\n--\s+", " "); Assert.Contains("Versioner:", header, StringComparison.Ordinal); Assert.Contains("Read-only:", header, StringComparison.Ordinal); Assert.Contains("ikke verificeret mod kundedata", header, StringComparison.Ordinal); Assert.Contains("sp_executesql", header, StringComparison.Ordinal); if (!file.StartsWith("COM-", StringComparison.Ordinal)) { // The AX table reads (the community rules read metadata only). Assert.Contains("aldrig NOLOCK", header, StringComparison.Ordinal); } Assert.DoesNotContain("PXI_", text, StringComparison.OrdinalIgnoreCase); if (!file.StartsWith('_')) { Assert.Contains("Alvor:", header, StringComparison.Ordinal); Assert.Contains("Rettigheder", header, StringComparison.Ordinal); } } private static string Folder { get { var directory = new DirectoryInfo(AppContext.BaseDirectory); while (directory is not null && !File.Exists(Path.Combine(directory.FullName, "SQLInsight.slnx"))) { directory = directory.Parent; } return Path.Combine(directory?.FullName ?? throw new InvalidOperationException("Repository-roden blev ikke fundet."), "SQL Scripts"); } } }