using ScadaLink.Commons.Messages.Notification; using ScadaLink.Commons.Types.Notifications; namespace ScadaLink.Commons.Tests.Messages; /// /// Notification Outbox: construction and value-equality tests for the /// site/central notification message contracts and the outbox UI query/action contracts. /// public class NotificationMessagesTests { // ── Task 7: site/central notification message contracts ── [Fact] public void NotificationSubmit_PositionalConstruction_SetsAllFields() { var enqueuedAt = DateTimeOffset.UtcNow; var msg = new NotificationSubmit( "notif-1", "Operators", "Tank overflow", "Tank 3 has overflowed.", "site-01", "inst-7", "OnAlarm", enqueuedAt); Assert.Equal("notif-1", msg.NotificationId); Assert.Equal("Operators", msg.ListName); Assert.Equal("Tank overflow", msg.Subject); Assert.Equal("Tank 3 has overflowed.", msg.Body); Assert.Equal("site-01", msg.SourceSiteId); Assert.Equal("inst-7", msg.SourceInstanceId); Assert.Equal("OnAlarm", msg.SourceScript); Assert.Equal(enqueuedAt, msg.SiteEnqueuedAt); } [Fact] public void NotificationSubmit_AllowsNullOptionalSourceFields() { var msg = new NotificationSubmit( "notif-2", "Operators", "Subject", "Body", "site-01", null, null, DateTimeOffset.UtcNow); Assert.Null(msg.SourceInstanceId); Assert.Null(msg.SourceScript); } [Fact] public void NotificationSubmit_ValueEquality_EqualWhenAllFieldsMatch() { var enqueuedAt = DateTimeOffset.UtcNow; var a = new NotificationSubmit("n", "L", "S", "B", "site", "inst", "scr", enqueuedAt); var b = new NotificationSubmit("n", "L", "S", "B", "site", "inst", "scr", enqueuedAt); Assert.Equal(a, b); Assert.Equal(a.GetHashCode(), b.GetHashCode()); } [Fact] public void NotificationSubmitAck_WithExpression_ChangesSingleField() { var ack = new NotificationSubmitAck("notif-1", true, null); var rejected = ack with { Accepted = false, Error = "duplicate" }; Assert.True(ack.Accepted); Assert.False(rejected.Accepted); Assert.Equal("duplicate", rejected.Error); Assert.Equal("notif-1", rejected.NotificationId); Assert.NotEqual(ack, rejected); } [Fact] public void NotificationStatusQuery_PositionalConstruction_SetsAllFields() { var msg = new NotificationStatusQuery("corr-1", "notif-9"); Assert.Equal("corr-1", msg.CorrelationId); Assert.Equal("notif-9", msg.NotificationId); } [Fact] public void NotificationStatusResponse_PositionalConstruction_SetsAllFields() { var deliveredAt = DateTimeOffset.UtcNow; var msg = new NotificationStatusResponse( "corr-1", true, "Delivered", 2, null, deliveredAt); Assert.Equal("corr-1", msg.CorrelationId); Assert.True(msg.Found); Assert.Equal("Delivered", msg.Status); Assert.Equal(2, msg.RetryCount); Assert.Null(msg.LastError); Assert.Equal(deliveredAt, msg.DeliveredAt); } // ── Task 8: outbox UI query/action contracts ── [Fact] public void NotificationOutboxQueryRequest_PositionalConstruction_SetsAllFields() { var from = DateTimeOffset.UtcNow.AddDays(-1); var to = DateTimeOffset.UtcNow; var msg = new NotificationOutboxQueryRequest( "corr-1", "Stuck", "Email", "site-01", "Operators", true, "overflow", from, to, 2, 50); Assert.Equal("corr-1", msg.CorrelationId); Assert.Equal("Stuck", msg.StatusFilter); Assert.Equal("Email", msg.TypeFilter); Assert.Equal("site-01", msg.SourceSiteFilter); Assert.Equal("Operators", msg.ListNameFilter); Assert.True(msg.StuckOnly); Assert.Equal("overflow", msg.SubjectKeyword); Assert.Equal(from, msg.From); Assert.Equal(to, msg.To); Assert.Equal(2, msg.PageNumber); Assert.Equal(50, msg.PageSize); } [Fact] public void NotificationSummary_ValueEquality_EqualWhenAllFieldsMatch() { var createdAt = DateTimeOffset.UtcNow; var a = new NotificationSummary( "n", "Email", "Ops", "S", "Pending", 1, null, "site-01", "inst-1", createdAt, null, false); var b = new NotificationSummary( "n", "Email", "Ops", "S", "Pending", 1, null, "site-01", "inst-1", createdAt, null, false); Assert.Equal(a, b); Assert.Equal(a.GetHashCode(), b.GetHashCode()); } [Fact] public void NotificationOutboxQueryResponse_PositionalConstruction_SetsAllFields() { var summary = new NotificationSummary( "n", "Email", "Ops", "S", "Delivered", 0, null, "site-01", null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, false); var msg = new NotificationOutboxQueryResponse( "corr-1", true, null, new[] { summary }, 1); Assert.Equal("corr-1", msg.CorrelationId); Assert.True(msg.Success); Assert.Null(msg.ErrorMessage); Assert.Single(msg.Notifications); Assert.Equal(1, msg.TotalCount); } [Fact] public void RetryNotificationRequestAndResponse_RoundTripFields() { var request = new RetryNotificationRequest("corr-1", "notif-1"); var response = new RetryNotificationResponse("corr-1", true, null); Assert.Equal("corr-1", request.CorrelationId); Assert.Equal("notif-1", request.NotificationId); Assert.True(response.Success); Assert.Null(response.ErrorMessage); } [Fact] public void DiscardNotificationRequestAndResponse_RoundTripFields() { var request = new DiscardNotificationRequest("corr-1", "notif-1"); var response = new DiscardNotificationResponse("corr-1", false, "not found"); Assert.Equal("notif-1", request.NotificationId); Assert.False(response.Success); Assert.Equal("not found", response.ErrorMessage); } [Fact] public void NotificationKpiResponse_WithExpression_ChangesSingleField() { var kpi = new NotificationKpiResponse( "corr-1", Success: true, ErrorMessage: null, 10, 2, 1, 5, TimeSpan.FromMinutes(3)); var updated = kpi with { QueueDepth = 12 }; Assert.True(kpi.Success); Assert.Null(kpi.ErrorMessage); Assert.Equal(10, kpi.QueueDepth); Assert.Equal(12, updated.QueueDepth); Assert.Equal(2, updated.StuckCount); Assert.Equal(TimeSpan.FromMinutes(3), updated.OldestPendingAge); Assert.NotEqual(kpi, updated); } [Fact] public void NotificationKpiRequest_PositionalConstruction_SetsCorrelationId() { var msg = new NotificationKpiRequest("corr-1"); Assert.Equal("corr-1", msg.CorrelationId); } [Fact] public void PerSiteNotificationKpiRequest_CarriesCorrelationId() { var request = new PerSiteNotificationKpiRequest("corr-1"); Assert.Equal("corr-1", request.CorrelationId); } [Fact] public void PerSiteNotificationKpiResponse_CarriesPerSiteSnapshots() { var sites = new[] { new SiteNotificationKpiSnapshot("plant-a", 3, 1, 0, 10, TimeSpan.FromMinutes(4)), }; var response = new PerSiteNotificationKpiResponse("corr-1", Success: true, ErrorMessage: null, sites); Assert.True(response.Success); Assert.Null(response.ErrorMessage); Assert.Single(response.Sites); Assert.Equal("plant-a", response.Sites[0].SourceSiteId); } }