using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using StellaOps.Notifier.Worker.Escalation; using Xunit; namespace StellaOps.Notifier.WebService.Tests.Escalation; /// /// Tests for escalation policy service. /// public sealed class EscalationPolicyServiceTests { private readonly FakeTimeProvider _timeProvider; private readonly InMemoryEscalationPolicyService _service; public EscalationPolicyServiceTests() { _timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero)); _service = new InMemoryEscalationPolicyService( null, _timeProvider, NullLogger.Instance); } [Fact] public async Task ListPolicies_WhenEmpty_ReturnsEmptyList() { var result = await _service.ListPoliciesAsync("tenant-1"); result.Should().BeEmpty(); } [Fact] public async Task UpsertPolicy_CreatesNewPolicy() { var policy = CreateTestPolicy("tenant-1", "policy-1"); var result = await _service.UpsertPolicyAsync(policy, "admin"); result.PolicyId.Should().Be("policy-1"); result.TenantId.Should().Be("tenant-1"); result.Name.Should().Be("Default Escalation"); result.Levels.Should().HaveCount(2); } [Fact] public async Task UpsertPolicy_UpdatesExistingPolicy() { var policy = CreateTestPolicy("tenant-1", "policy-1"); await _service.UpsertPolicyAsync(policy, "admin"); var updated = policy with { Name = "Updated Policy" }; var result = await _service.UpsertPolicyAsync(updated, "admin"); result.Name.Should().Be("Updated Policy"); var retrieved = await _service.GetPolicyAsync("tenant-1", "policy-1"); retrieved!.Name.Should().Be("Updated Policy"); } [Fact] public async Task GetPolicy_WhenExists_ReturnsPolicy() { var policy = CreateTestPolicy("tenant-1", "policy-1"); await _service.UpsertPolicyAsync(policy, "admin"); var result = await _service.GetPolicyAsync("tenant-1", "policy-1"); result.Should().NotBeNull(); result!.PolicyId.Should().Be("policy-1"); } [Fact] public async Task GetPolicy_WhenNotExists_ReturnsNull() { var result = await _service.GetPolicyAsync("tenant-1", "nonexistent"); result.Should().BeNull(); } [Fact] public async Task DeletePolicy_WhenExists_ReturnsTrue() { var policy = CreateTestPolicy("tenant-1", "policy-1"); await _service.UpsertPolicyAsync(policy, "admin"); var result = await _service.DeletePolicyAsync("tenant-1", "policy-1", "admin"); result.Should().BeTrue(); var retrieved = await _service.GetPolicyAsync("tenant-1", "policy-1"); retrieved.Should().BeNull(); } [Fact] public async Task DeletePolicy_WhenNotExists_ReturnsFalse() { var result = await _service.DeletePolicyAsync("tenant-1", "nonexistent", "admin"); result.Should().BeFalse(); } [Fact] public async Task GetDefaultPolicy_ReturnsFirstDefaultPolicy() { var policy1 = CreateTestPolicy("tenant-1", "policy-1") with { IsDefault = false }; var policy2 = CreateTestPolicy("tenant-1", "policy-2") with { IsDefault = true }; var policy3 = CreateTestPolicy("tenant-1", "policy-3") with { IsDefault = true }; await _service.UpsertPolicyAsync(policy1, "admin"); await _service.UpsertPolicyAsync(policy2, "admin"); await _service.UpsertPolicyAsync(policy3, "admin"); var result = await _service.GetDefaultPolicyAsync("tenant-1"); result.Should().NotBeNull(); result!.IsDefault.Should().BeTrue(); } [Fact] public async Task GetDefaultPolicy_WhenNoneDefault_ReturnsNull() { var policy = CreateTestPolicy("tenant-1", "policy-1") with { IsDefault = false }; await _service.UpsertPolicyAsync(policy, "admin"); var result = await _service.GetDefaultPolicyAsync("tenant-1"); result.Should().BeNull(); } [Fact] public async Task FindMatchingPolicies_FiltersByEventKind() { var policy1 = CreateTestPolicy("tenant-1", "policy-1") with { EventKindFilter = ["scan.*", "vulnerability.*"] }; var policy2 = CreateTestPolicy("tenant-1", "policy-2") with { EventKindFilter = ["compliance.*"] }; await _service.UpsertPolicyAsync(policy1, "admin"); await _service.UpsertPolicyAsync(policy2, "admin"); var result = await _service.FindMatchingPoliciesAsync("tenant-1", "scan.completed", null); result.Should().HaveCount(1); result[0].PolicyId.Should().Be("policy-1"); } [Fact] public async Task FindMatchingPolicies_FiltersBySeverity() { var policy1 = CreateTestPolicy("tenant-1", "policy-1") with { SeverityFilter = ["critical", "high"] }; var policy2 = CreateTestPolicy("tenant-1", "policy-2") with { SeverityFilter = ["low"] }; await _service.UpsertPolicyAsync(policy1, "admin"); await _service.UpsertPolicyAsync(policy2, "admin"); var result = await _service.FindMatchingPoliciesAsync("tenant-1", "incident.created", "critical"); result.Should().HaveCount(1); result[0].PolicyId.Should().Be("policy-1"); } [Fact] public async Task FindMatchingPolicies_ReturnsAllWhenNoFilters() { var policy1 = CreateTestPolicy("tenant-1", "policy-1"); var policy2 = CreateTestPolicy("tenant-1", "policy-2"); await _service.UpsertPolicyAsync(policy1, "admin"); await _service.UpsertPolicyAsync(policy2, "admin"); var result = await _service.FindMatchingPoliciesAsync("tenant-1", "any.event", null); result.Should().HaveCount(2); } [Fact] public async Task ListPolicies_IsolatesByTenant() { var policy1 = CreateTestPolicy("tenant-1", "policy-1"); var policy2 = CreateTestPolicy("tenant-2", "policy-2"); await _service.UpsertPolicyAsync(policy1, "admin"); await _service.UpsertPolicyAsync(policy2, "admin"); var tenant1Policies = await _service.ListPoliciesAsync("tenant-1"); var tenant2Policies = await _service.ListPoliciesAsync("tenant-2"); tenant1Policies.Should().HaveCount(1); tenant1Policies[0].PolicyId.Should().Be("policy-1"); tenant2Policies.Should().HaveCount(1); tenant2Policies[0].PolicyId.Should().Be("policy-2"); } [Fact] public async Task UpsertPolicy_SetsTimestamps() { var policy = CreateTestPolicy("tenant-1", "policy-1"); var result = await _service.UpsertPolicyAsync(policy, "admin"); result.CreatedAt.Should().Be(_timeProvider.GetUtcNow()); result.UpdatedAt.Should().Be(_timeProvider.GetUtcNow()); } private static EscalationPolicy CreateTestPolicy(string tenantId, string policyId) => new() { PolicyId = policyId, TenantId = tenantId, Name = "Default Escalation", IsDefault = true, Levels = [ new EscalationLevel { Order = 1, DelayMinutes = 5, Targets = [ new EscalationTarget { TargetType = EscalationTargetType.OnCallSchedule, TargetId = "schedule-1" } ] }, new EscalationLevel { Order = 2, DelayMinutes = 15, Targets = [ new EscalationTarget { TargetType = EscalationTargetType.User, TargetId = "manager-1" } ] } ] }; }