using ScadaLink.Commons.Messages.Audit; using ScadaLink.Commons.Types.Audit; namespace ScadaLink.Commons.Tests.Messages; /// /// Site Call Audit (#22): construction, value-equality and optionality tests /// for the Site Calls UI query / KPI / detail message contracts. Mirrors the /// Notification Outbox NotificationMessagesTests coverage of the read /// side, scoped to the contracts the Site Calls page consumes. /// public class SiteCallQueriesTests { [Fact] public void SiteCallQueryRequest_PositionalConstruction_SetsAllFields() { var afterCreated = DateTime.UtcNow; var afterId = Guid.NewGuid(); var request = new SiteCallQueryRequest( "corr-1", "Parked", "plant-a", "ApiOutbound", "ERP.GetOrder", true, new DateTime(2026, 5, 1), new DateTime(2026, 5, 20), afterCreated, afterId, 50); Assert.Equal("corr-1", request.CorrelationId); Assert.Equal("Parked", request.StatusFilter); Assert.Equal("plant-a", request.SourceSiteFilter); Assert.Equal("ApiOutbound", request.ChannelFilter); Assert.Equal("ERP.GetOrder", request.TargetKeyword); Assert.True(request.StuckOnly); Assert.Equal(new DateTime(2026, 5, 1), request.FromUtc); Assert.Equal(new DateTime(2026, 5, 20), request.ToUtc); Assert.Equal(afterCreated, request.AfterCreatedAtUtc); Assert.Equal(afterId, request.AfterId); Assert.Equal(50, request.PageSize); } [Fact] public void SiteCallQueryRequest_AllowsNullOptionalFilters() { var request = new SiteCallQueryRequest( "corr-2", null, null, null, null, false, null, null, null, null, 25); Assert.Null(request.StatusFilter); Assert.Null(request.SourceSiteFilter); Assert.Null(request.ChannelFilter); Assert.Null(request.TargetKeyword); Assert.False(request.StuckOnly); Assert.Null(request.FromUtc); Assert.Null(request.AfterId); } [Fact] public void SiteCallQueryResponse_ValueEquality_EqualWhenAllFieldsMatch() { var a = new SiteCallQueryResponse("c", true, null, Array.Empty(), null, null); var b = new SiteCallQueryResponse("c", true, null, Array.Empty(), null, null); Assert.Equal(a, b); Assert.Equal(a.GetHashCode(), b.GetHashCode()); } [Fact] public void SiteCallSummary_CarriesEntityColumnsAndStuckFlag() { var id = Guid.NewGuid(); var created = DateTime.UtcNow.AddMinutes(-30); var summary = new SiteCallSummary( id, "plant-a", "DbOutbound", "InventoryDb", "Retrying", 3, "transient 503", 503, created, created.AddMinutes(1), null, IsStuck: true); Assert.Equal(id, summary.TrackedOperationId); Assert.Equal("DbOutbound", summary.Channel); Assert.Equal("InventoryDb", summary.Target); Assert.Equal("Retrying", summary.Status); Assert.Equal(3, summary.RetryCount); Assert.Equal(503, summary.HttpStatus); Assert.Null(summary.TerminalAtUtc); Assert.True(summary.IsStuck); } [Fact] public void SiteCallDetailResponse_MissingRow_HasNullDetail() { var response = new SiteCallDetailResponse("c", false, "site call not found", null); Assert.False(response.Success); Assert.Null(response.Detail); Assert.Equal("site call not found", response.ErrorMessage); } [Fact] public void SiteCallKpiResponse_FailureShape_ZeroesKpiFields() { var response = new SiteCallKpiResponse( "c", Success: false, ErrorMessage: "db down", BufferedCount: 0, ParkedCount: 0, FailedLastInterval: 0, DeliveredLastInterval: 0, OldestPendingAge: null, StuckCount: 0); Assert.False(response.Success); Assert.Equal("db down", response.ErrorMessage); Assert.Equal(0, response.BufferedCount); Assert.Null(response.OldestPendingAge); } [Fact] public void PerSiteSiteCallKpiResponse_CarriesPerSiteSnapshots() { var response = new PerSiteSiteCallKpiResponse( "c", true, null, new[] { new SiteCallSiteKpiSnapshot("plant-a", 4, 1, 0, 9, TimeSpan.FromMinutes(15), 2), }); Assert.True(response.Success); var site = Assert.Single(response.Sites); Assert.Equal("plant-a", site.SourceSite); Assert.Equal(4, site.BufferedCount); Assert.Equal(2, site.StuckCount); Assert.Equal(TimeSpan.FromMinutes(15), site.OldestPendingAge); } [Fact] public void SiteCallKpiSnapshot_OldestPendingAge_IsNullableForEmptyTable() { var snapshot = new SiteCallKpiSnapshot(0, 0, 0, 0, null, 0); Assert.Null(snapshot.OldestPendingAge); } }