using Microsoft.AspNetCore.WebUtilities; using ScadaLink.Commons.Types.Audit; using ScadaLink.Commons.Types.Enums; using AuditLogPage = ScadaLink.CentralUI.Components.Pages.Audit.AuditLogPage; namespace ScadaLink.CentralUI.Tests.Pages; /// /// Unit tests for (#23 M7-T14 / /// Bundle F). Builds the ?... querystring the Export-CSV link points /// at; the same conversion is round-tripped on the server side by /// . /// These tests pin the no-filter base path + the round-trip back through /// so the link contract stays stable. /// public class AuditLogPageExportUrlTests { [Fact] public void BuildExportUrl_NullFilter_ReturnsBasePath() { var url = AuditLogPage.BuildExportUrl(null); Assert.Equal("/api/centralui/audit/export", url); } [Fact] public void BuildExportUrl_EmptyFilter_ReturnsBasePath() { // Defensive: a filter where every column is null should still render // as the bare path — no trailing "?" so the URL stays clean. var url = AuditLogPage.BuildExportUrl(new AuditLogQueryFilter()); Assert.Equal("/api/centralui/audit/export", url); } [Fact] public void BuildExportUrl_AllFiltersSet_RoundTrips() { var corr = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"); var filter = new AuditLogQueryFilter( Channels: new[] { AuditChannel.ApiOutbound }, Kinds: new[] { AuditKind.ApiCall }, Statuses: new[] { AuditStatus.Failed }, SourceSiteIds: new[] { "plant-a" }, Target: "PaymentApi", Actor: "apikey-1", CorrelationId: corr, FromUtc: new DateTime(2026, 5, 20, 0, 0, 0, DateTimeKind.Utc), ToUtc: new DateTime(2026, 5, 20, 23, 59, 59, DateTimeKind.Utc)); var url = AuditLogPage.BuildExportUrl(filter); Assert.StartsWith("/api/centralui/audit/export?", url); var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query); Assert.Equal("ApiOutbound", query["channel"]); Assert.Equal("ApiCall", query["kind"]); Assert.Equal("Failed", query["status"]); Assert.Equal("plant-a", query["site"]); Assert.Equal("PaymentApi", query["target"]); Assert.Equal("apikey-1", query["actor"]); Assert.Equal(corr.ToString(), query["correlationId"]); Assert.Equal("2026-05-20T00:00:00.0000000Z", query["from"]); Assert.Equal("2026-05-20T23:59:59.0000000Z", query["to"]); } [Fact] public void BuildExportUrl_OnlyChannelSet_OmitsOtherParams() { var filter = new AuditLogQueryFilter(Channels: new[] { AuditChannel.Notification }); var url = AuditLogPage.BuildExportUrl(filter); Assert.StartsWith("/api/centralui/audit/export?", url); var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query); Assert.Single(query); Assert.Equal("Notification", query["channel"]); } [Fact] public void BuildExportUrl_ExecutionIdSet_EmitsExecutionIdParam() { var exec = Guid.Parse("12121212-3434-5656-7878-909090909090"); var filter = new AuditLogQueryFilter(ExecutionId: exec); var url = AuditLogPage.BuildExportUrl(filter); Assert.StartsWith("/api/centralui/audit/export?", url); var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query); Assert.Single(query); Assert.Equal(exec.ToString(), query["executionId"]); } [Fact] public void BuildExportUrl_ParentExecutionIdSet_EmitsParentExecutionIdParam() { var parent = Guid.Parse("34343434-5656-7878-9090-121212121212"); var filter = new AuditLogQueryFilter(ParentExecutionId: parent); var url = AuditLogPage.BuildExportUrl(filter); Assert.StartsWith("/api/centralui/audit/export?", url); var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query); Assert.Single(query); Assert.Equal(parent.ToString(), query["parentExecutionId"]); } [Fact] public void BuildExportUrl_MultiValueDimensions_EmitRepeatedParams() { // Task 9: each multi-value dimension emits one repeated query-string key // per selected value so the export endpoint's ParseFilter sees them all. var filter = new AuditLogQueryFilter( Channels: new[] { AuditChannel.ApiOutbound, AuditChannel.DbOutbound }, Statuses: new[] { AuditStatus.Failed, AuditStatus.Parked }, SourceSiteIds: new[] { "plant-a", "plant-b" }); var url = AuditLogPage.BuildExportUrl(filter); var query = QueryHelpers.ParseQuery(new Uri("http://x" + url).Query); Assert.Equal(new[] { "ApiOutbound", "DbOutbound" }, query["channel"].ToArray()); Assert.Equal(new[] { "Failed", "Parked" }, query["status"].ToArray()); Assert.Equal(new[] { "plant-a", "plant-b" }, query["site"].ToArray()); } }