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");
}
}
}