The AX scripts written in 1.4 (own header style and a generator) now follow main's "SQL Scripts" structure: the review header, "-- Kilde:" blocks registered in SqlScriptSources and checked word for word against the code. - SqlScriptSources: the shared AX detection (DatabasesSql, LoginSql, BuildDetectionSql), companies, batch jobs, batch history (first read and from the watermark) and number sequences / RecId for Axapta 3.0, AX 4.0, AX 2009 and AX 2012; BlockingCollector split 2008 / 2005, as CONTEXT_INFO comes from sys.dm_exec_sessions from 2008 and from sysprocesses on 2005. - SQLI-AX-001..010 and _collectors/Ax*.sql in main's format; COM-AX-001..007 with the 1.4 queries, the AX detection and USE [AXDB]; BLK-001, BlockingCollector and DatabaseDiscovery updated (CONTEXT_INFO, sp_executesql). - Scripts_are_read_only stays strict (no INTO #temp anywhere) and now also checks the batch inside EXEC [db].sys.sp_executesql N'...'. - SQLINSIGHT_UPDATE_SQL_SCRIPTS=1 (from 1.4) rewrites out-of-date blocks from the code; the 1.4 generator test now checks that every AX script shows its sources for every AX version and says what is unverified. - README: SQLI-AX-001..010, the AX collector scripts, 108 rules. - AxDatabaseDetector: LoginSql and BuildDetectionSql as members, so the registry sees them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
111 lines
5.5 KiB
C#
111 lines
5.5 KiB
C#
using SQLInsight.Collectors.Tests.SqlScripts;
|
|
|
|
namespace SQLInsight.Collectors.Tests;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <see cref="SqlScripts.SqlScriptTests"/> (the <c>-- Kilde:</c> blocks, registered in <see cref="SqlScriptSources"/>);
|
|
/// rewrite out-of-date blocks with SQLINSIGHT_UPDATE_SQL_SCRIPTS=1.
|
|
/// </summary>
|
|
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})")];
|
|
|
|
/// <summary>File → the sources (keys) whose SQL it must show.</summary>
|
|
internal static Dictionary<string, string[]> Expected()
|
|
{
|
|
var files = new Dictionary<string, string[]>(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<string> 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");
|
|
}
|
|
}
|
|
}
|