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"
}
]
}
]
};
}