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
357 lines
13 KiB
C#
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
|
|
};
|
|
}
|