// ----------------------------------------------------------------------------- // CounterfactualEndpointsTests.cs // Sprint: SPRINT_4200_0002_0005_counterfactuals // Description: Integration tests for counterfactual analysis endpoints. // ----------------------------------------------------------------------------- using System.Net; using System.Net.Http.Json; using System.Text.Json; using StellaOps.Scanner.WebService.Endpoints; namespace StellaOps.Scanner.WebService.Tests; /// /// Integration tests for counterfactual analysis endpoints. /// public sealed class CounterfactualEndpointsTests { private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web); [Fact] public async Task PostCompute_ValidRequest_ReturnsCounterfactuals() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", Purl = "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1", CurrentVerdict = "Block" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Equal("finding-123", result!.FindingId); Assert.Equal("Block", result.CurrentVerdict); Assert.True(result.HasPaths); Assert.NotEmpty(result.Paths); Assert.NotEmpty(result.WouldPassIf); } [Fact] public async Task PostCompute_MissingFindingId_ReturnsBadRequest() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "", VulnId = "CVE-2021-44228" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); } [Fact] public async Task PostCompute_IncludesVexPath() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", CurrentVerdict = "Block" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Contains(result!.Paths, p => p.Type == "Vex"); } [Fact] public async Task PostCompute_IncludesReachabilityPath() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", CurrentVerdict = "Block" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Contains(result!.Paths, p => p.Type == "Reachability"); } [Fact] public async Task PostCompute_IncludesExceptionPath() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", CurrentVerdict = "Block" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Contains(result!.Paths, p => p.Type == "Exception"); } [Fact] public async Task PostCompute_WithMaxPaths_LimitsResults() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", CurrentVerdict = "Block", MaxPaths = 2 }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.True(result!.Paths.Count <= 2); } [Fact] public async Task GetForFinding_ValidId_ReturnsCounterfactuals() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var response = await client.GetAsync("/api/v1/counterfactuals/finding/finding-123"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Equal("finding-123", result!.FindingId); } [Fact] public async Task GetScanSummary_ValidId_ReturnsSummary() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var response = await client.GetAsync("/api/v1/counterfactuals/scan/scan-123/summary"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.Equal("scan-123", result!.ScanId); Assert.NotNull(result.Findings); } [Fact] public async Task GetScanSummary_IncludesPathCounts() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var response = await client.GetAsync("/api/v1/counterfactuals/scan/scan-123/summary"); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); Assert.True(result!.TotalBlocked >= 0); Assert.True(result.WithVexPath >= 0); Assert.True(result.WithReachabilityPath >= 0); Assert.True(result.WithUpgradePath >= 0); Assert.True(result.WithExceptionPath >= 0); } [Fact] public async Task PostCompute_PathsHaveConditions() { using var factory = new ScannerApplicationFactory(); using var client = factory.CreateClient(); var request = new CounterfactualRequestDto { FindingId = "finding-123", VulnId = "CVE-2021-44228", CurrentVerdict = "Block" }; var response = await client.PostAsJsonAsync("/api/v1/counterfactuals/compute", request); var result = await response.Content.ReadFromJsonAsync(SerializerOptions); Assert.NotNull(result); foreach (var path in result!.Paths) { Assert.NotEmpty(path.Description); Assert.NotEmpty(path.Conditions); foreach (var condition in path.Conditions) { Assert.NotEmpty(condition.Field); Assert.NotEmpty(condition.RequiredValue); } } } }