Files
git.stella-ops.org/src/Notifier/StellaOps.Notifier/StellaOps.Notifier.WebService.Tests/Escalation/InboxChannelTests.cs
StellaOps Bot ef6e4b2067
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Merge branch 'main' of https://git.stella-ops.org/stella-ops.org/git.stella-ops.org
2025-11-27 21:45:32 +02:00

357 lines
13 KiB
C#

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;
/// <summary>
/// Tests for inbox channel adapters.
/// </summary>
public sealed class InboxChannelTests
{
private readonly FakeTimeProvider _timeProvider;
private readonly InAppInboxChannel _inboxChannel;
private readonly CliNotificationChannel _cliChannel;
public InboxChannelTests()
{
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero));
_inboxChannel = new InAppInboxChannel(
null,
_timeProvider,
NullLogger<InAppInboxChannel>.Instance);
_cliChannel = new CliNotificationChannel(
_timeProvider,
NullLogger<CliNotificationChannel>.Instance);
}
[Fact]
public async Task InApp_SendAsync_CreatesNotification()
{
var notification = CreateTestNotification("tenant-1", "user-1", "notif-1");
var result = await _inboxChannel.SendAsync(notification);
result.Success.Should().BeTrue();
result.NotificationId.Should().Be("notif-1");
}
[Fact]
public async Task InApp_ListAsync_ReturnsNotifications()
{
var notification = CreateTestNotification("tenant-1", "user-1", "notif-1");
await _inboxChannel.SendAsync(notification);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1");
result.Should().HaveCount(1);
result[0].NotificationId.Should().Be("notif-1");
}
[Fact]
public async Task InApp_ListAsync_FiltersUnread()
{
var notif1 = CreateTestNotification("tenant-1", "user-1", "notif-1");
var notif2 = CreateTestNotification("tenant-1", "user-1", "notif-2");
await _inboxChannel.SendAsync(notif1);
await _inboxChannel.SendAsync(notif2);
await _inboxChannel.MarkReadAsync("tenant-1", "user-1", "notif-1");
var result = await _inboxChannel.ListAsync("tenant-1", "user-1", new InboxQuery { IsRead = false });
result.Should().HaveCount(1);
result[0].NotificationId.Should().Be("notif-2");
}
[Fact]
public async Task InApp_ListAsync_FiltersByType()
{
var incident = CreateTestNotification("tenant-1", "user-1", "notif-1") with
{
Type = InboxNotificationType.Incident
};
var system = CreateTestNotification("tenant-1", "user-1", "notif-2") with
{
Type = InboxNotificationType.System
};
await _inboxChannel.SendAsync(incident);
await _inboxChannel.SendAsync(system);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1",
new InboxQuery { Type = InboxNotificationType.Incident });
result.Should().HaveCount(1);
result[0].NotificationId.Should().Be("notif-1");
}
[Fact]
public async Task InApp_ListAsync_FiltersByMinPriority()
{
var low = CreateTestNotification("tenant-1", "user-1", "notif-1") with { Priority = InboxPriority.Low };
var high = CreateTestNotification("tenant-1", "user-1", "notif-2") with { Priority = InboxPriority.High };
var urgent = CreateTestNotification("tenant-1", "user-1", "notif-3") with { Priority = InboxPriority.Urgent };
await _inboxChannel.SendAsync(low);
await _inboxChannel.SendAsync(high);
await _inboxChannel.SendAsync(urgent);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1",
new InboxQuery { MinPriority = InboxPriority.High });
result.Should().HaveCount(2);
result.Should().OnlyContain(n => n.Priority >= InboxPriority.High);
}
[Fact]
public async Task InApp_ListAsync_ExcludesExpired()
{
var active = CreateTestNotification("tenant-1", "user-1", "notif-1") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(1)
};
var expired = CreateTestNotification("tenant-1", "user-1", "notif-2") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(-1)
};
await _inboxChannel.SendAsync(active);
await _inboxChannel.SendAsync(expired);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1");
result.Should().HaveCount(1);
result[0].NotificationId.Should().Be("notif-1");
}
[Fact]
public async Task InApp_ListAsync_IncludesExpiredWhenRequested()
{
var active = CreateTestNotification("tenant-1", "user-1", "notif-1") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(1)
};
var expired = CreateTestNotification("tenant-1", "user-1", "notif-2") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(-1)
};
await _inboxChannel.SendAsync(active);
await _inboxChannel.SendAsync(expired);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1",
new InboxQuery { IncludeExpired = true });
result.Should().HaveCount(2);
}
[Fact]
public async Task InApp_ListAsync_RespectsLimit()
{
for (int i = 0; i < 10; i++)
{
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", $"notif-{i}"));
}
var result = await _inboxChannel.ListAsync("tenant-1", "user-1",
new InboxQuery { Limit = 5 });
result.Should().HaveCount(5);
}
[Fact]
public async Task InApp_MarkReadAsync_MarksNotificationAsRead()
{
var notification = CreateTestNotification("tenant-1", "user-1", "notif-1");
await _inboxChannel.SendAsync(notification);
var result = await _inboxChannel.MarkReadAsync("tenant-1", "user-1", "notif-1");
result.Should().BeTrue();
var list = await _inboxChannel.ListAsync("tenant-1", "user-1");
list[0].IsRead.Should().BeTrue();
list[0].ReadAt.Should().Be(_timeProvider.GetUtcNow());
}
[Fact]
public async Task InApp_MarkReadAsync_ReturnsFalseForNonexistent()
{
var result = await _inboxChannel.MarkReadAsync("tenant-1", "user-1", "nonexistent");
result.Should().BeFalse();
}
[Fact]
public async Task InApp_MarkAllReadAsync_MarksAllAsRead()
{
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-1"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-2"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-3"));
var result = await _inboxChannel.MarkAllReadAsync("tenant-1", "user-1");
result.Should().Be(3);
var unread = await _inboxChannel.GetUnreadCountAsync("tenant-1", "user-1");
unread.Should().Be(0);
}
[Fact]
public async Task InApp_GetUnreadCountAsync_ReturnsCorrectCount()
{
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-1"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-2"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-3"));
await _inboxChannel.MarkReadAsync("tenant-1", "user-1", "notif-1");
var result = await _inboxChannel.GetUnreadCountAsync("tenant-1", "user-1");
result.Should().Be(2);
}
[Fact]
public async Task InApp_GetUnreadCountAsync_ExcludesExpired()
{
var active = CreateTestNotification("tenant-1", "user-1", "notif-1") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(1)
};
var expired = CreateTestNotification("tenant-1", "user-1", "notif-2") with
{
ExpiresAt = _timeProvider.GetUtcNow().AddHours(-1)
};
await _inboxChannel.SendAsync(active);
await _inboxChannel.SendAsync(expired);
var result = await _inboxChannel.GetUnreadCountAsync("tenant-1", "user-1");
result.Should().Be(1);
}
[Fact]
public async Task InApp_IsolatesByTenantAndUser()
{
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-1", "notif-1"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-1", "user-2", "notif-2"));
await _inboxChannel.SendAsync(CreateTestNotification("tenant-2", "user-1", "notif-3"));
var tenant1User1 = await _inboxChannel.ListAsync("tenant-1", "user-1");
var tenant1User2 = await _inboxChannel.ListAsync("tenant-1", "user-2");
var tenant2User1 = await _inboxChannel.ListAsync("tenant-2", "user-1");
tenant1User1.Should().HaveCount(1).And.Contain(n => n.NotificationId == "notif-1");
tenant1User2.Should().HaveCount(1).And.Contain(n => n.NotificationId == "notif-2");
tenant2User1.Should().HaveCount(1).And.Contain(n => n.NotificationId == "notif-3");
}
[Fact]
public async Task InApp_ListAsync_SortsByPriorityAndCreatedAt()
{
var low = CreateTestNotification("tenant-1", "user-1", "notif-low") with { Priority = InboxPriority.Low };
await _inboxChannel.SendAsync(low);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
var high = CreateTestNotification("tenant-1", "user-1", "notif-high") with { Priority = InboxPriority.High };
await _inboxChannel.SendAsync(high);
_timeProvider.Advance(TimeSpan.FromMinutes(1));
var urgent = CreateTestNotification("tenant-1", "user-1", "notif-urgent") with { Priority = InboxPriority.Urgent };
await _inboxChannel.SendAsync(urgent);
var result = await _inboxChannel.ListAsync("tenant-1", "user-1");
result[0].NotificationId.Should().Be("notif-urgent");
result[1].NotificationId.Should().Be("notif-high");
result[2].NotificationId.Should().Be("notif-low");
}
[Fact]
public async Task Cli_SendAsync_CreatesNotification()
{
var notification = CreateTestNotification("tenant-1", "user-1", "notif-1");
var result = await _cliChannel.SendAsync(notification);
result.Success.Should().BeTrue();
result.NotificationId.Should().Be("notif-1");
}
[Fact]
public async Task Cli_ListAsync_ReturnsNotifications()
{
var notification = CreateTestNotification("tenant-1", "user-1", "notif-1");
await _cliChannel.SendAsync(notification);
var result = await _cliChannel.ListAsync("tenant-1", "user-1");
result.Should().HaveCount(1);
}
[Fact]
public void Cli_FormatForCli_FormatsCorrectly()
{
var notification = new InboxNotification
{
NotificationId = "notif-1",
TenantId = "tenant-1",
UserId = "user-1",
Type = InboxNotificationType.Incident,
Title = "Critical Alert",
Body = "Server down",
Priority = InboxPriority.Urgent,
IsRead = false,
CreatedAt = new DateTimeOffset(2025, 1, 15, 10, 0, 0, TimeSpan.Zero)
};
var formatted = CliNotificationChannel.FormatForCli(notification);
formatted.Should().Contain("[!!!]");
formatted.Should().Contain("●");
formatted.Should().Contain("Critical Alert");
formatted.Should().Contain("Server down");
formatted.Should().Contain("2025-01-15");
}
[Fact]
public void Cli_FormatForCli_ShowsReadMarker()
{
var notification = new InboxNotification
{
NotificationId = "notif-1",
TenantId = "tenant-1",
UserId = "user-1",
Type = InboxNotificationType.Incident,
Title = "Alert",
Body = "Details",
Priority = InboxPriority.Normal,
IsRead = true,
CreatedAt = _timeProvider.GetUtcNow()
};
var formatted = CliNotificationChannel.FormatForCli(notification);
formatted.Should().NotContain("●");
formatted.Should().Contain("[*]");
}
private static InboxNotification CreateTestNotification(string tenantId, string userId, string notificationId) => new()
{
NotificationId = notificationId,
TenantId = tenantId,
UserId = userId,
Type = InboxNotificationType.Incident,
Title = "Test Alert",
Body = "This is a test notification",
Priority = InboxPriority.Normal
};
}