// -----------------------------------------------------------------------------
// 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);
}
}
}
}