using SQLInsight.Core.Findings; using SQLInsight.Core.Instances; using SQLInsight.Rules.Engine; namespace SQLInsight.Rules.Tests; public class RuleEngineTests { private sealed class StubRule(string id, IReadOnlyCollection required, Func evaluate) : IRule { public int Calls { get; private set; } public string Id => id; public string Title => $"Stub {id}"; public IReadOnlyCollection RequiredCapabilities => required; public RuleEvaluation Evaluate(RuleContext context) { Calls++; return evaluate(context); } } [Fact] public void Rule_missing_capabilities_is_not_measurable_and_never_runs() { var rule = new StubRule("QS", [Capability.QueryStore], _ => RuleEvaluation.Passed()); var engine = new RuleEngine([rule]); var result = Assert.Single(engine.Evaluate(TestInstances.Context("11.0.7001.0"))); Assert.Equal(RuleOutcome.NotMeasurable, result.Outcome); Assert.Equal([Capability.QueryStore], result.MissingCapabilities); Assert.Contains("SQL Server 2012", result.Reason, StringComparison.Ordinal); Assert.Equal(0, rule.Calls); } [Fact] public void Rule_with_capabilities_runs() { var rule = new StubRule("QS", [Capability.QueryStore], _ => RuleEvaluation.Passed()); var engine = new RuleEngine([rule]); var result = Assert.Single(engine.Evaluate(TestInstances.Context("16.0.4135.4"))); Assert.Equal(RuleOutcome.Passed, result.Outcome); Assert.Equal(1, rule.Calls); } [Fact] public void Throwing_rule_is_reported_as_error_and_does_not_stop_others() { var failing = new StubRule("A", [], _ => throw new InvalidOperationException("boom")); var finding = new Finding { RuleId = "B", Severity = Severity.Low, Title = "t", Explanation = "e", Recommendation = "r", }; var working = new StubRule("B", [], _ => RuleEvaluation.Found(finding)); var engine = new RuleEngine([failing, working]); var results = engine.Evaluate(TestInstances.Context("16.0.4135.4")); Assert.Equal(RuleOutcome.Error, results[0].Outcome); Assert.Contains("boom", results[0].Reason, StringComparison.Ordinal); Assert.Equal(RuleOutcome.Findings, results[1].Outcome); Assert.Same(finding, Assert.Single(results[1].Findings)); } [Fact] public void Duplicate_rule_ids_are_rejected() { var a = new StubRule("SAME", [], _ => RuleEvaluation.Passed()); var b = new StubRule("same", [], _ => RuleEvaluation.Passed()); Assert.Throws(() => new RuleEngine([a, b])); } [Fact] public void Found_requires_at_least_one_finding() { Assert.Throws(() => RuleEvaluation.Found()); } [Fact] public void Built_in_rule_ids_are_unique() { var engine = new RuleEngine(BuiltInRules.Create()); Assert.NotEmpty(engine.Evaluate(TestInstances.Context("16.0.4135.4"))); } }