// SPDX-License-Identifier: BUSL-1.1 // Sprint: SPRINT_5200_0001_0001 - Starter Policy Template // Task: T6 - Starter Policy Tests using System.Text.Json; using System.Text.Json.Nodes; using FluentAssertions; using Json.Schema; using YamlDotNet.Serialization; using StellaOps.TestKit; namespace StellaOps.Policy.Pack.Tests; public class StarterPolicyPackTests { private readonly string _testDataPath; private readonly IDeserializer _yamlDeserializer; public StarterPolicyPackTests() { _testDataPath = Path.Combine(AppContext.BaseDirectory, "TestData"); _yamlDeserializer = new DeserializerBuilder() .WithNamingConvention(YamlDotNet.Serialization.NamingConventions.CamelCaseNamingConvention.Instance) .Build(); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_Exists() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); File.Exists(policyPath).Should().BeTrue("starter-day1.yaml should exist"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasValidYamlStructure() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var act = () => _yamlDeserializer.Deserialize>(content); act.Should().NotThrow("YAML should be valid and parseable"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasRequiredFields() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); policy.Should().ContainKey("apiVersion", "Policy should have apiVersion field"); policy.Should().ContainKey("kind", "Policy should have kind field"); policy.Should().ContainKey("metadata", "Policy should have metadata field"); policy.Should().ContainKey("spec", "Policy should have spec field"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasCorrectApiVersion() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); policy["apiVersion"].Should().Be("policy.stellaops.io/v1"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasCorrectKind() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); policy["kind"].Should().Be("PolicyPack"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasValidMetadata() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); var metadata = policy["metadata"] as Dictionary; metadata.Should().NotBeNull(); metadata!.Should().ContainKey("name"); metadata.Should().ContainKey("version"); metadata.Should().ContainKey("description"); metadata["name"].Should().Be("starter-day1"); metadata["version"].ToString().Should().MatchRegex(@"^\d+\.\d+\.\d+(-[a-zA-Z0-9]+)?$", "version should be semver"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasRulesSection() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); var spec = policy["spec"] as Dictionary; spec.Should().NotBeNull(); spec!.Should().ContainKey("rules"); var rules = spec["rules"] as List; rules.Should().NotBeNull(); rules!.Should().HaveCountGreaterThan(0, "Policy should have at least one rule"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasSettingsSection() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); var spec = policy["spec"] as Dictionary; spec.Should().NotBeNull(); spec!.Should().ContainKey("settings"); var settings = spec["settings"] as Dictionary; settings.Should().NotBeNull(); settings!.Should().ContainKey("defaultAction"); } [Trait("Category", TestCategories.Unit)] [Theory] [InlineData("block-reachable-high-critical")] [InlineData("warn-reachable-medium")] [InlineData("allow-unreachable")] [InlineData("fail-on-unknowns")] [InlineData("block-kev")] [InlineData("default-allow")] public void StarterDay1Policy_ContainsExpectedRule(string ruleName) { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); var spec = policy["spec"] as Dictionary; var rules = spec!["rules"] as List; var ruleNames = rules!.Cast>() .Select(r => r["name"]?.ToString()) .Where(n => n != null) .ToList(); ruleNames.Should().Contain(ruleName, $"Policy should contain rule '{ruleName}'"); } [Trait("Category", TestCategories.Unit)] [Fact] public void StarterDay1Policy_HasDefaultAllowRuleWithLowestPriority() { var policyPath = Path.Combine(_testDataPath, "starter-day1.yaml"); var content = File.ReadAllText(policyPath); var policy = _yamlDeserializer.Deserialize>(content); var spec = policy["spec"] as Dictionary; var rules = spec!["rules"] as List; var defaultAllowRule = rules!.Cast>() .FirstOrDefault(r => r["name"]?.ToString() == "default-allow"); defaultAllowRule.Should().NotBeNull("Policy should have a default-allow rule"); var priority = Convert.ToInt32(defaultAllowRule!["priority"]); priority.Should().Be(0, "default-allow rule should have the lowest priority (0)"); var action = defaultAllowRule["action"]?.ToString(); action.Should().Be("allow", "default-allow rule should have action 'allow'"); } }