using System; using System.Collections.Generic; using System.Linq; namespace StellaOps.Notify.Models.Tests; public sealed class NotifyRuleTests { [Fact] public void ConstructorThrowsWhenActionsMissing() { var match = NotifyRuleMatch.Create(eventKinds: new[] { NotifyEventKinds.ScannerReportReady }); var exception = Assert.Throws(() => NotifyRule.Create( ruleId: "rule-1", tenantId: "tenant-a", name: "critical", match: match, actions: Array.Empty())); Assert.Contains("At least one action is required", exception.Message, StringComparison.Ordinal); } [Fact] public void ConstructorNormalizesCollections() { var rule = NotifyRule.Create( ruleId: "rule-1", tenantId: "tenant-a", name: "critical", match: NotifyRuleMatch.Create( eventKinds: new[] { "Zastava.Admission", NotifyEventKinds.ScannerReportReady }), actions: new[] { NotifyRuleAction.Create(actionId: "b", channel: "slack:sec-alerts", throttle: TimeSpan.FromMinutes(5)), NotifyRuleAction.Create(actionId: "a", channel: "email:soc", metadata: new Dictionary { [" locale "] = " EN-us " }) }, labels: new Dictionary { [" team "] = " SecOps " }, metadata: new Dictionary { ["source"] = "tests" }); Assert.Equal(NotifySchemaVersions.Rule, rule.SchemaVersion); Assert.Equal(new[] { "scanner.report.ready", "zastava.admission" }, rule.Match.EventKinds); Assert.Equal(new[] { "a", "b" }, rule.Actions.Select(action => action.ActionId)); Assert.Equal(TimeSpan.FromMinutes(5), rule.Actions.Last().Throttle); Assert.Equal("secops", rule.Labels.Single().Value.ToLowerInvariant()); Assert.Equal("en-us", rule.Actions.First().Metadata["locale"].ToLowerInvariant()); var json = NotifyCanonicalJsonSerializer.Serialize(rule); Assert.Contains("\"schemaVersion\":\"notify.rule@1\"", json, StringComparison.Ordinal); Assert.Contains("\"actions\":[{\"actionId\":\"a\"", json, StringComparison.Ordinal); Assert.Contains("\"throttle\":\"PT5M\"", json, StringComparison.Ordinal); } }