Refactor code structure and optimize performance across multiple modules

This commit is contained in:
StellaOps Bot
2025-12-26 20:03:22 +02:00
parent c786faae84
commit b4fc66feb6
3353 changed files with 88254 additions and 1590657 deletions

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -18,7 +20,8 @@ public sealed class ActionablesEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDeltaActionables_ValidDeltaId_ReturnsActionables()
{
using var factory = new ScannerApplicationFactory();
@@ -33,7 +36,8 @@ public sealed class ActionablesEndpointsTests
Assert.NotNull(result.Actionables);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDeltaActionables_SortedByPriority()
{
using var factory = new ScannerApplicationFactory();
@@ -50,7 +54,8 @@ public sealed class ActionablesEndpointsTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActionablesByPriority_Critical_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -64,7 +69,8 @@ public sealed class ActionablesEndpointsTests
Assert.All(result!.Actionables, a => Assert.Equal("critical", a.Priority, StringComparer.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActionablesByPriority_InvalidPriority_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -74,7 +80,8 @@ public sealed class ActionablesEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActionablesByType_Upgrade_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -88,7 +95,8 @@ public sealed class ActionablesEndpointsTests
Assert.All(result!.Actionables, a => Assert.Equal("upgrade", a.Type, StringComparer.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActionablesByType_Vex_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -102,7 +110,8 @@ public sealed class ActionablesEndpointsTests
Assert.All(result!.Actionables, a => Assert.Equal("vex", a.Type, StringComparer.OrdinalIgnoreCase));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetActionablesByType_InvalidType_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -112,7 +121,8 @@ public sealed class ActionablesEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDeltaActionables_IncludesEstimatedEffort()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -19,6 +19,7 @@ using Xunit;
using MsOptions = Microsoft.Extensions.Options;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -50,7 +51,8 @@ public sealed class AttestationChainVerifierTests
#region VerifyChainAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_ValidInput_ReturnsResult()
{
// Arrange
@@ -64,7 +66,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_NoAttestationsFound_ReturnsEmptyStatus()
{
// Arrange
@@ -80,7 +83,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Attestations.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_BothAttestationsValid_ReturnsComplete()
{
// Arrange
@@ -97,7 +101,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Attestations.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_OnlyRichGraphAttestationValid_ReturnsPartial()
{
// Arrange
@@ -117,7 +122,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Attestations.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_ExpiredAttestation_ReturnsExpiredStatus()
{
// Arrange
@@ -132,14 +138,16 @@ public sealed class AttestationChainVerifierTests
result.Chain!.Status.Should().Be(ChainStatus.Expired);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_NullInput_ThrowsArgumentNullException()
{
await Assert.ThrowsAsync<ArgumentNullException>(() =>
_verifier.VerifyChainAsync(null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_EmptyFindingId_ThrowsArgumentException()
{
var input = new ChainVerificationInput
@@ -153,7 +161,8 @@ public sealed class AttestationChainVerifierTests
_verifier.VerifyChainAsync(input));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_EmptyRootDigest_ThrowsArgumentException()
{
var input = new ChainVerificationInput
@@ -167,7 +176,8 @@ public sealed class AttestationChainVerifierTests
_verifier.VerifyChainAsync(input));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_WithGracePeriod_AllowsRecentlyExpired()
{
// Arrange
@@ -186,7 +196,8 @@ public sealed class AttestationChainVerifierTests
result.Chain!.Status.Should().NotBe(ChainStatus.Invalid);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_WithHumanApproval_IncludesInChain()
{
// Arrange
@@ -205,7 +216,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Attestations.Should().Contain(a => a.Type == AttestationType.HumanApproval);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_RequiresHumanApproval_PartialWhenMissing()
{
// Arrange
@@ -222,7 +234,8 @@ public sealed class AttestationChainVerifierTests
result.Chain.Attestations.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_ExpiredHumanApproval_ReturnsExpiredStatus()
{
// Arrange
@@ -238,7 +251,8 @@ public sealed class AttestationChainVerifierTests
result.Chain!.Status.Should().Be(ChainStatus.Expired);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task VerifyChainAsync_RevokedHumanApproval_ReturnsInvalidStatus()
{
// Arrange
@@ -261,7 +275,8 @@ public sealed class AttestationChainVerifierTests
#region GetChainAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetChainAsync_ValidInput_ReturnsChain()
{
// Arrange
@@ -280,7 +295,8 @@ public sealed class AttestationChainVerifierTests
chain.Should().BeNull("GetChainAsync is currently a placeholder implementation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetChainAsync_NoAttestations_ReturnsNull()
{
// Arrange
@@ -298,7 +314,8 @@ public sealed class AttestationChainVerifierTests
#region IsChainComplete Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsChainComplete_AllRequiredTypes_ReturnsTrue()
{
// Arrange
@@ -316,7 +333,8 @@ public sealed class AttestationChainVerifierTests
isComplete.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsChainComplete_MissingRequiredType_ReturnsFalse()
{
// Arrange
@@ -332,7 +350,8 @@ public sealed class AttestationChainVerifierTests
isComplete.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsChainComplete_EmptyChain_ReturnsFalse()
{
// Arrange
@@ -345,7 +364,8 @@ public sealed class AttestationChainVerifierTests
isComplete.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsChainComplete_NoRequiredTypes_WithEmptyChain_ReturnsFalse()
{
// Arrange
@@ -360,7 +380,8 @@ public sealed class AttestationChainVerifierTests
isComplete.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void IsChainComplete_NoRequiredTypes_WithAttestations_ReturnsTrue()
{
// Arrange
@@ -379,7 +400,8 @@ public sealed class AttestationChainVerifierTests
#region GetEarliestExpiration Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetEarliestExpiration_MultipleAttestations_ReturnsEarliest()
{
// Arrange
@@ -394,7 +416,8 @@ public sealed class AttestationChainVerifierTests
earliest.Should().Be(earlier);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetEarliestExpiration_EmptyChain_ReturnsNull()
{
// Arrange
@@ -407,7 +430,8 @@ public sealed class AttestationChainVerifierTests
earliest.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetEarliestExpiration_SingleAttestation_ReturnsThatExpiry()
{
// Arrange
@@ -421,7 +445,8 @@ public sealed class AttestationChainVerifierTests
earliest.Should().Be(expiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetEarliestExpiration_NullChain_ThrowsArgumentNullException()
{
// Act & Assert
@@ -745,7 +770,8 @@ public sealed class AttestationChainVerifierTests
/// </summary>
public sealed class AttestationChainVerifierOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultGracePeriodMinutes_DefaultsTo60()
{
var options = new AttestationChainVerifierOptions();
@@ -753,7 +779,8 @@ public sealed class AttestationChainVerifierOptionsTests
options.DefaultGracePeriodMinutes.Should().Be(60);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RequireHumanApprovalForHighSeverity_DefaultsToTrue()
{
var options = new AttestationChainVerifierOptions();
@@ -761,7 +788,8 @@ public sealed class AttestationChainVerifierOptionsTests
options.RequireHumanApprovalForHighSeverity.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MaxChainDepth_DefaultsTo10()
{
var options = new AttestationChainVerifierOptions();
@@ -769,7 +797,8 @@ public sealed class AttestationChainVerifierOptionsTests
options.MaxChainDepth.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FailOnMissingAttestations_DefaultsToFalse()
{
var options = new AttestationChainVerifierOptions();
@@ -783,7 +812,8 @@ public sealed class AttestationChainVerifierOptionsTests
/// </summary>
public sealed class ChainStatusTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ChainStatus.Complete, "Complete")]
[InlineData(ChainStatus.Partial, "Partial")]
[InlineData(ChainStatus.Expired, "Expired")]
@@ -801,7 +831,8 @@ public sealed class ChainStatusTests
/// </summary>
public sealed class AttestationTypeTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(AttestationType.RichGraph, "RichGraph")]
[InlineData(AttestationType.PolicyDecision, "PolicyDecision")]
[InlineData(AttestationType.HumanApproval, "HumanApproval")]
@@ -818,7 +849,8 @@ public sealed class AttestationTypeTests
/// </summary>
public sealed class ChainVerificationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_CreatesSuccessResult()
{
var chain = CreateValidChain();
@@ -829,7 +861,8 @@ public sealed class ChainVerificationResultTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_WithDetails_IncludesDetails()
{
var chain = CreateValidChain();
@@ -849,7 +882,8 @@ public sealed class ChainVerificationResultTests
result.Details.Should().HaveCount(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failed_CreatesFailedResult()
{
var result = ChainVerificationResult.Failed("Test error");
@@ -859,7 +893,8 @@ public sealed class ChainVerificationResultTests
result.Error.Should().Be("Test error");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failed_WithChain_IncludesChain()
{
var chain = CreateValidChain();

View File

@@ -1,10 +1,13 @@
using System.Net;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class AuthorizationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApiRoutesRequireAuthenticationWhenAuthorityEnabled()
{
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -18,7 +20,8 @@ public sealed class BaselineEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRecommendations_ValidDigest_ReturnsRecommendations()
{
using var factory = new ScannerApplicationFactory();
@@ -34,7 +37,8 @@ public sealed class BaselineEndpointsTests
Assert.Contains(result.Recommendations, r => r.IsDefault);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRecommendations_WithEnvironment_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -48,7 +52,8 @@ public sealed class BaselineEndpointsTests
Assert.NotEmpty(result!.Recommendations);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRecommendations_IncludesRationale()
{
using var factory = new ScannerApplicationFactory();
@@ -66,7 +71,8 @@ public sealed class BaselineEndpointsTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRationale_ValidDigests_ReturnsDetailedRationale()
{
using var factory = new ScannerApplicationFactory();
@@ -84,7 +90,8 @@ public sealed class BaselineEndpointsTests
Assert.NotEmpty(result.DetailedExplanation);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRationale_IncludesSelectionCriteria()
{
using var factory = new ScannerApplicationFactory();
@@ -98,7 +105,8 @@ public sealed class BaselineEndpointsTests
Assert.NotEmpty(result.SelectionCriteria);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRecommendations_DefaultIsFirst()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -3,11 +3,14 @@ using System.Net.Http.Json;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class CallGraphEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitCallGraphRequiresContentDigestHeader()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -26,7 +29,8 @@ public sealed class CallGraphEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitCallGraphReturnsAcceptedAndDetectsDuplicates()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.Scanner.WebService.Endpoints;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -18,7 +20,8 @@ public sealed class CounterfactualEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_ValidRequest_ReturnsCounterfactuals()
{
using var factory = new ScannerApplicationFactory();
@@ -44,7 +47,8 @@ public sealed class CounterfactualEndpointsTests
Assert.NotEmpty(result.WouldPassIf);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_MissingFindingId_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -60,7 +64,8 @@ public sealed class CounterfactualEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_IncludesVexPath()
{
using var factory = new ScannerApplicationFactory();
@@ -80,7 +85,8 @@ public sealed class CounterfactualEndpointsTests
Assert.Contains(result!.Paths, p => p.Type == "Vex");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_IncludesReachabilityPath()
{
using var factory = new ScannerApplicationFactory();
@@ -100,7 +106,8 @@ public sealed class CounterfactualEndpointsTests
Assert.Contains(result!.Paths, p => p.Type == "Reachability");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_IncludesExceptionPath()
{
using var factory = new ScannerApplicationFactory();
@@ -120,7 +127,8 @@ public sealed class CounterfactualEndpointsTests
Assert.Contains(result!.Paths, p => p.Type == "Exception");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_WithMaxPaths_LimitsResults()
{
using var factory = new ScannerApplicationFactory();
@@ -141,7 +149,8 @@ public sealed class CounterfactualEndpointsTests
Assert.True(result!.Paths.Count <= 2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetForFinding_ValidId_ReturnsCounterfactuals()
{
using var factory = new ScannerApplicationFactory();
@@ -155,7 +164,8 @@ public sealed class CounterfactualEndpointsTests
Assert.Equal("finding-123", result!.FindingId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetScanSummary_ValidId_ReturnsSummary()
{
using var factory = new ScannerApplicationFactory();
@@ -170,7 +180,8 @@ public sealed class CounterfactualEndpointsTests
Assert.NotNull(result.Findings);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetScanSummary_IncludesPathCounts()
{
using var factory = new ScannerApplicationFactory();
@@ -187,7 +198,8 @@ public sealed class CounterfactualEndpointsTests
Assert.True(result.WithExceptionPath >= 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompute_PathsHaveConditions()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -18,7 +20,8 @@ public sealed class DeltaCompareEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompare_ValidRequest_ReturnsComparisonResult()
{
using var factory = new ScannerApplicationFactory();
@@ -46,7 +49,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.Equal("sha256:target456", result.Target.Digest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompare_MissingBaseDigest_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -62,7 +66,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompare_MissingTargetDigest_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -78,7 +83,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetQuickDiff_ValidDigests_ReturnsQuickSummary()
{
using var factory = new ScannerApplicationFactory();
@@ -95,7 +101,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.NotEmpty(result.Summary);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetQuickDiff_MissingDigest_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -105,7 +112,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetComparison_NotFound_ReturnsNotFound()
{
using var factory = new ScannerApplicationFactory();
@@ -115,7 +123,8 @@ public sealed class DeltaCompareEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostCompare_DeterministicComparisonId_SameInputsSameId()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -12,13 +12,16 @@ using StellaOps.Scanner.WebService.Endpoints;
using Xunit;
using FluentAssertions;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class EvidenceEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsBadRequest_WhenScanIdInvalid()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -34,7 +37,8 @@ public sealed class EvidenceEndpointsTests
response.StatusCode.Should().Be(HttpStatusCode.NotFound); // Route doesn't match
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsNotFound_WhenScanDoesNotExist()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -50,7 +54,8 @@ public sealed class EvidenceEndpointsTests
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsListEndpoint_WhenFindingIdEmpty()
{
// When no finding ID is provided, the route matches the list endpoint
@@ -71,7 +76,8 @@ public sealed class EvidenceEndpointsTests
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListEvidence_ReturnsEmptyList_WhenNoFindings()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -93,7 +99,8 @@ public sealed class EvidenceEndpointsTests
result.Items.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListEvidence_ReturnsEmptyList_WhenScanDoesNotExist()
{
// The current implementation returns empty list for non-existent scans
@@ -134,7 +141,8 @@ public sealed class EvidenceEndpointsTests
/// </summary>
public sealed class EvidenceTtlTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultEvidenceTtlDays_DefaultsToSevenDays()
{
// Verify the default configuration
@@ -143,7 +151,8 @@ public sealed class EvidenceTtlTests
options.DefaultEvidenceTtlDays.Should().Be(7);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexEvidenceTtlDays_DefaultsToThirtyDays()
{
var options = new StellaOps.Scanner.WebService.Services.EvidenceCompositionOptions();
@@ -151,7 +160,8 @@ public sealed class EvidenceTtlTests
options.VexEvidenceTtlDays.Should().Be(30);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void StaleWarningThresholdDays_DefaultsToOne()
{
var options = new StellaOps.Scanner.WebService.Services.EvidenceCompositionOptions();
@@ -159,7 +169,8 @@ public sealed class EvidenceTtlTests
options.StaleWarningThresholdDays.Should().Be(1);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceCompositionOptions_CanBeConfigured()
{
var options = new StellaOps.Scanner.WebService.Services.EvidenceCompositionOptions

View File

@@ -10,6 +10,7 @@ using System.Text.Json;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public class FindingEvidenceContractsTests
@@ -20,7 +21,8 @@ public class FindingEvidenceContractsTests
WriteIndented = false
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingEvidenceResponse_SerializesToSnakeCase()
{
var response = new FindingEvidenceResponse
@@ -49,7 +51,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"freshness\":", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingEvidenceResponse_RoundTripsCorrectly()
{
var original = new FindingEvidenceResponse
@@ -98,7 +101,8 @@ public class FindingEvidenceContractsTests
Assert.Equal(original.Score?.RiskScore, deserialized.Score?.RiskScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComponentInfo_SerializesAllFields()
{
var component = new ComponentInfo
@@ -117,7 +121,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"ecosystem\":\"nuget\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EntrypointInfo_SerializesAllFields()
{
var entrypoint = new EntrypointInfo
@@ -136,7 +141,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"auth\":\"mtls\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BoundaryInfo_SerializesWithControls()
{
var boundary = new BoundaryInfo
@@ -153,7 +159,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"controls\":[\"waf\",\"rate_limit\"]", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexStatusInfo_SerializesCorrectly()
{
var vex = new VexStatusInfo
@@ -171,7 +178,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"issuer\":\"vendor\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScoreInfo_SerializesContributions()
{
var score = new ScoreInfo
@@ -201,7 +209,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"factor\":\"reachability\"", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FreshnessInfo_SerializesCorrectly()
{
var freshness = new FreshnessInfo
@@ -218,7 +227,8 @@ public class FindingEvidenceContractsTests
Assert.Contains("\"ttl_remaining_hours\":0", json);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NullOptionalFields_AreOmittedOrNullInJson()
{
var response = new FindingEvidenceResponse

View File

@@ -8,13 +8,16 @@ using StellaOps.Scanner.Triage.Entities;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class FindingsEvidenceControllerTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsNotFound_WhenFindingMissing()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -29,7 +32,8 @@ public sealed class FindingsEvidenceControllerTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsForbidden_WhenRawScopeMissing()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -44,7 +48,8 @@ public sealed class FindingsEvidenceControllerTests
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEvidence_ReturnsEvidence_WhenFindingExists()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -66,7 +71,8 @@ public sealed class FindingsEvidenceControllerTests
Assert.Equal("CVE-2024-12345", result.Cve);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsBadRequest_WhenTooMany()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -86,7 +92,8 @@ public sealed class FindingsEvidenceControllerTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task BatchEvidence_ReturnsResults_ForExistingFindings()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -10,6 +10,7 @@ using FluentAssertions;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -21,7 +22,8 @@ public sealed class GatingContractsSerializationTests
#region GatingReason Enum Serialization
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(GatingReason.None, "none")]
[InlineData(GatingReason.Unreachable, "unreachable")]
[InlineData(GatingReason.PolicyDismissed, "policyDismissed")]
@@ -38,7 +40,8 @@ public sealed class GatingContractsSerializationTests
json.Should().Contain($"\"gatingReason\":{(int)reason}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatingReason_AllValuesAreDefined()
{
// Ensure all expected reasons are defined
@@ -49,7 +52,8 @@ public sealed class GatingContractsSerializationTests
#region FindingGatingStatusDto Serialization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_SerializesAllFields()
{
var dto = new FindingGatingStatusDto
@@ -74,7 +78,8 @@ public sealed class GatingContractsSerializationTests
deserialized.WouldShowIf.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_HandlesNullOptionalFields()
{
var dto = new FindingGatingStatusDto
@@ -93,7 +98,8 @@ public sealed class GatingContractsSerializationTests
deserialized.WouldShowIf.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_DefaultsToNotHidden()
{
var dto = new FindingGatingStatusDto();
@@ -106,7 +112,8 @@ public sealed class GatingContractsSerializationTests
#region VexTrustBreakdownDto Serialization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexTrustBreakdownDto_SerializesAllComponents()
{
var dto = new VexTrustBreakdownDto
@@ -129,7 +136,8 @@ public sealed class GatingContractsSerializationTests
deserialized.ConsensusScore.Should().Be(0.85);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexTrustBreakdownDto_ConsensusScoreIsOptional()
{
var dto = new VexTrustBreakdownDto
@@ -151,7 +159,8 @@ public sealed class GatingContractsSerializationTests
#region TriageVexTrustStatusDto Serialization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageVexTrustStatusDto_SerializesWithBreakdown()
{
var vexStatus = new TriageVexStatusDto
@@ -189,7 +198,8 @@ public sealed class GatingContractsSerializationTests
#region GatedBucketsSummaryDto Serialization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_SerializesAllCounts()
{
var dto = new GatedBucketsSummaryDto
@@ -214,7 +224,8 @@ public sealed class GatingContractsSerializationTests
deserialized.UserMutedCount.Should().Be(5);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_Empty_ReturnsZeroCounts()
{
var dto = GatedBucketsSummaryDto.Empty;
@@ -227,7 +238,8 @@ public sealed class GatingContractsSerializationTests
dto.UserMutedCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_TotalHiddenCount_SumsAllBuckets()
{
var dto = new GatedBucketsSummaryDto
@@ -247,7 +259,8 @@ public sealed class GatingContractsSerializationTests
#region BulkTriageQueryWithGatingResponseDto Serialization
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BulkTriageQueryWithGatingResponseDto_IncludesGatedBuckets()
{
var dto = new BulkTriageQueryWithGatingResponseDto
@@ -278,7 +291,8 @@ public sealed class GatingContractsSerializationTests
#region Snapshot Tests (JSON Structure)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_SnapshotTest_JsonStructure()
{
var dto = new FindingGatingStatusDto
@@ -306,7 +320,8 @@ public sealed class GatingContractsSerializationTests
json.Should().Contain("\"wouldShowIf\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_SnapshotTest_JsonStructure()
{
var dto = new GatedBucketsSummaryDto

View File

@@ -11,6 +11,7 @@ using StellaOps.Scanner.Triage.Entities;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -22,7 +23,8 @@ public sealed class GatingReasonServiceTests
{
#region GTR-9200-019: Gating Reason Path Tests - Entity Model Validation
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(GatingReason.None, false)]
[InlineData(GatingReason.Unreachable, true)]
[InlineData(GatingReason.PolicyDismissed, true)]
@@ -44,7 +46,8 @@ public sealed class GatingReasonServiceTests
dto.IsHiddenByDefault.Should().Be(expectedHidden);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_UserMuted_HasExpectedExplanation()
{
// Arrange
@@ -62,7 +65,8 @@ public sealed class GatingReasonServiceTests
dto.WouldShowIf.Should().Contain("Un-mute the finding in triage settings");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_PolicyDismissed_HasPolicyIdInExplanation()
{
// Arrange
@@ -80,7 +84,8 @@ public sealed class GatingReasonServiceTests
dto.WouldShowIf.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_VexNotAffected_IncludesTrustInfo()
{
// Arrange
@@ -97,7 +102,8 @@ public sealed class GatingReasonServiceTests
dto.GatingExplanation.Should().Contain("trust");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_Backported_IncludesFixedVersion()
{
// Arrange
@@ -114,7 +120,8 @@ public sealed class GatingReasonServiceTests
dto.GatingExplanation.Should().Contain(fixedVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_Superseded_IncludesSupersedingCve()
{
// Arrange
@@ -131,7 +138,8 @@ public sealed class GatingReasonServiceTests
dto.GatingExplanation.Should().Contain(supersedingCve);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_Unreachable_HasSubgraphId()
{
// Arrange
@@ -150,7 +158,8 @@ public sealed class GatingReasonServiceTests
dto.GatingExplanation.Should().Contain("not reachable");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingGatingStatusDto_None_IsNotHidden()
{
// Arrange
@@ -170,7 +179,8 @@ public sealed class GatingReasonServiceTests
#region GTR-9200-020: Bucket Counting Logic Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_Empty_ReturnsZeroCounts()
{
// Arrange & Act
@@ -186,7 +196,8 @@ public sealed class GatingReasonServiceTests
dto.TotalHiddenCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_TotalHiddenCount_SumsAllBuckets()
{
// Arrange
@@ -204,7 +215,8 @@ public sealed class GatingReasonServiceTests
dto.TotalHiddenCount.Should().Be(28);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatedBucketsSummaryDto_WithMixedCounts_CalculatesCorrectly()
{
// Arrange
@@ -224,7 +236,8 @@ public sealed class GatingReasonServiceTests
dto.VexNotAffectedCount.Should().Be(12);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BulkTriageQueryWithGatingResponseDto_IncludesGatedBuckets()
{
// Arrange
@@ -249,7 +262,8 @@ public sealed class GatingReasonServiceTests
dto.GatedBuckets!.TotalHiddenCount.Should().Be(28);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BulkTriageQueryWithGatingRequestDto_SupportsGatingReasonFilter()
{
// Arrange
@@ -267,7 +281,8 @@ public sealed class GatingReasonServiceTests
dto.GatingReasonFilter.Should().Contain(GatingReason.VexNotAffected);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void BulkTriageQueryWithGatingRequestDto_DefaultsToNotIncludeHidden()
{
// Arrange
@@ -285,7 +300,8 @@ public sealed class GatingReasonServiceTests
#region GTR-9200-021: VEX Trust Threshold Comparison Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexTrustBreakdownDto_AllComponents_SumToCompositeScore()
{
// Arrange - weights: issuer=0.4, recency=0.2, justification=0.2, evidence=0.2
@@ -305,7 +321,8 @@ public sealed class GatingReasonServiceTests
compositeScore.Should().Be(1.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexTrustBreakdownDto_LowIssuerTrust_ReducesCompositeScore()
{
// Arrange - unknown issuer has low trust (0.5)
@@ -325,7 +342,8 @@ public sealed class GatingReasonServiceTests
compositeScore.Should().Be(0.8);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageVexTrustStatusDto_MeetsPolicyThreshold_WhenTrustExceedsThreshold()
{
// Arrange
@@ -344,7 +362,8 @@ public sealed class GatingReasonServiceTests
dto.MeetsPolicyThreshold.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageVexTrustStatusDto_DoesNotMeetThreshold_WhenTrustBelowThreshold()
{
// Arrange
@@ -363,7 +382,8 @@ public sealed class GatingReasonServiceTests
dto.MeetsPolicyThreshold.Should().BeFalse();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("nvd", 1.0)]
[InlineData("redhat", 0.95)]
[InlineData("canonical", 0.95)]
@@ -377,7 +397,8 @@ public sealed class GatingReasonServiceTests
expectedTrust.Should().BeGreaterOrEqualTo(0.9);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexRecencyTrust_RecentStatement_HasHighTrust()
{
// Arrange - VEX from within a week
@@ -388,7 +409,8 @@ public sealed class GatingReasonServiceTests
age.TotalDays.Should().BeLessThan(7);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexRecencyTrust_OldStatement_HasLowTrust()
{
// Arrange - VEX from over a year ago
@@ -399,7 +421,8 @@ public sealed class GatingReasonServiceTests
age.TotalDays.Should().BeGreaterThan(365);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexJustificationTrust_DetailedJustification_HasHighTrust()
{
// Arrange - 500+ chars = trust 1.0
@@ -409,7 +432,8 @@ public sealed class GatingReasonServiceTests
justification.Length.Should().BeGreaterOrEqualTo(500);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexJustificationTrust_ShortJustification_HasLowTrust()
{
// Arrange - < 50 chars = trust 0.4
@@ -419,7 +443,8 @@ public sealed class GatingReasonServiceTests
justification.Length.Should().BeLessThan(50);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexEvidenceTrust_SignedWithLedger_HasHighTrust()
{
// Arrange - DSSE envelope + signature ref + source ref
@@ -439,7 +464,8 @@ public sealed class GatingReasonServiceTests
vex.SourceRef.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexEvidenceTrust_NoEvidence_HasBaseTrust()
{
// Arrange - no signature, no ledger, no source
@@ -462,7 +488,8 @@ public sealed class GatingReasonServiceTests
#region Edge Cases and Entity Model Validation
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageFinding_RequiredFields_AreSet()
{
// Arrange
@@ -479,7 +506,8 @@ public sealed class GatingReasonServiceTests
finding.Purl.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriagePolicyDecision_PolicyActions_AreValid()
{
// Valid actions: dismiss, waive, tolerate, block
@@ -498,7 +526,8 @@ public sealed class GatingReasonServiceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageEffectiveVex_VexStatuses_AreAllDefined()
{
// Arrange
@@ -510,7 +539,8 @@ public sealed class GatingReasonServiceTests
statuses.Should().Contain(TriageVexStatus.UnderInvestigation);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageReachability_Values_AreAllDefined()
{
// Arrange
@@ -522,7 +552,8 @@ public sealed class GatingReasonServiceTests
values.Should().Contain(TriageReachability.Unknown);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TriageReachabilityResult_RequiredInputsHash_IsSet()
{
// Arrange
@@ -538,7 +569,8 @@ public sealed class GatingReasonServiceTests
result.InputsHash.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GatingReason_AllValues_HaveCorrectNumericMapping()
{
// Document the enum values for API stability
@@ -551,7 +583,8 @@ public sealed class GatingReasonServiceTests
GatingReason.UserMuted.Should().Be((GatingReason)6);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void FindingTriageStatusWithGatingDto_CombinesBaseStatusWithGating()
{
// Arrange

View File

@@ -1,10 +1,13 @@
using System.Net.Http.Json;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class HealthEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HealthAndReadyEndpointsRespond()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -18,6 +18,7 @@ using Xunit;
using MsOptions = Microsoft.Extensions.Options;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -39,7 +40,8 @@ public sealed class HumanApprovalAttestationServiceTests
#region CreateAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_ReturnsSuccessResult()
{
// Arrange
@@ -56,7 +58,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_CreatesInTotoStatement()
{
// Arrange
@@ -71,7 +74,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement.PredicateType.Should().Be("stella.ops/human-approval@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesSubjects()
{
// Arrange
@@ -88,7 +92,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement.Subject[1].Digest.Should().ContainKey("sha256");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesApproverInfo()
{
// Arrange
@@ -104,7 +109,8 @@ public sealed class HumanApprovalAttestationServiceTests
approver.Role.Should().Be(input.ApproverRole);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesDecisionAndJustification()
{
// Arrange
@@ -118,7 +124,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement.Predicate.Justification.Should().Be(input.Justification);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_DefaultTtl_SetsExpiresAtTo30Days()
{
// Arrange
@@ -132,7 +139,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_CustomTtl_SetsExpiresAtToCustomValue()
{
// Arrange
@@ -146,7 +154,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_SetsApprovedAtToCurrentTime()
{
// Arrange
@@ -160,7 +169,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement!.Predicate.ApprovedAt.Should().Be(expectedTime);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_IncludesOptionalPolicyDecisionRef()
{
// Arrange
@@ -173,7 +183,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement!.Predicate.PolicyDecisionRef.Should().Be("sha256:policy123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_IncludesRestrictions()
{
// Arrange
@@ -195,7 +206,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement.Predicate.Restrictions.MaxInstances.Should().Be(100);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_GeneratesUniqueApprovalId()
{
// Arrange
@@ -210,7 +222,8 @@ public sealed class HumanApprovalAttestationServiceTests
result1.Statement!.Predicate.ApprovalId.Should().NotBe(result2.Statement!.Predicate.ApprovalId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_NullInput_ThrowsArgumentNullException()
{
// Act & Assert
@@ -218,7 +231,8 @@ public sealed class HumanApprovalAttestationServiceTests
_service.CreateAttestationAsync(null!));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyFindingId_ThrowsArgumentException(string findingId)
@@ -231,7 +245,8 @@ public sealed class HumanApprovalAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyApproverUserId_ThrowsArgumentException(string userId)
@@ -244,7 +259,8 @@ public sealed class HumanApprovalAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyJustification_ThrowsArgumentException(string justification)
@@ -257,7 +273,8 @@ public sealed class HumanApprovalAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ApprovalDecision.AcceptRisk)]
[InlineData(ApprovalDecision.Defer)]
[InlineData(ApprovalDecision.Reject)]
@@ -280,7 +297,8 @@ public sealed class HumanApprovalAttestationServiceTests
#region GetAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_ExistingAttestation_ReturnsAttestation()
{
// Arrange
@@ -296,7 +314,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Statement!.Predicate.FindingId.Should().Be(input.FindingId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_NonExistentAttestation_ReturnsNull()
{
// Act
@@ -306,7 +325,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_ExpiredAttestation_ReturnsNull()
{
// Arrange
@@ -325,7 +345,8 @@ public sealed class HumanApprovalAttestationServiceTests
// In production, expiration would be checked against current time
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_WrongScanId_ReturnsNull()
{
// Arrange
@@ -339,7 +360,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_EmptyFindingId_ReturnsNull()
{
// Arrange
@@ -357,7 +379,8 @@ public sealed class HumanApprovalAttestationServiceTests
#region GetApprovalsByScanAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetApprovalsByScanAsync_MultipleApprovals_ReturnsAll()
{
// Arrange
@@ -375,7 +398,8 @@ public sealed class HumanApprovalAttestationServiceTests
results.Should().HaveCount(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetApprovalsByScanAsync_NoApprovals_ReturnsEmptyList()
{
// Act
@@ -385,7 +409,8 @@ public sealed class HumanApprovalAttestationServiceTests
results.Should().BeEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetApprovalsByScanAsync_ExcludesRevokedApprovals()
{
// Arrange
@@ -405,7 +430,8 @@ public sealed class HumanApprovalAttestationServiceTests
#region RevokeApprovalAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeApprovalAsync_ExistingApproval_ReturnsTrue()
{
// Arrange
@@ -423,7 +449,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeApprovalAsync_NonExistentApproval_ReturnsFalse()
{
// Act
@@ -437,7 +464,8 @@ public sealed class HumanApprovalAttestationServiceTests
result.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RevokeApprovalAsync_MarksAttestationAsRevoked()
{
// Arrange
@@ -453,7 +481,8 @@ public sealed class HumanApprovalAttestationServiceTests
result!.IsRevoked.Should().BeTrue();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task RevokeApprovalAsync_EmptyRevokedBy_ThrowsArgumentException(string revokedBy)
@@ -471,7 +500,8 @@ public sealed class HumanApprovalAttestationServiceTests
#region Serialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_SerializesToValidJson()
{
// Arrange
@@ -489,7 +519,8 @@ public sealed class HumanApprovalAttestationServiceTests
json.Should().Contain("\"approver\":");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_Schema_IsHumanApprovalV1()
{
// Arrange
@@ -541,7 +572,8 @@ public sealed class HumanApprovalAttestationServiceTests
/// </summary>
public sealed class HumanApprovalAttestationOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultApprovalTtlDays_DefaultsTo30()
{
var options = new HumanApprovalAttestationOptions();
@@ -549,7 +581,8 @@ public sealed class HumanApprovalAttestationOptionsTests
options.DefaultApprovalTtlDays.Should().Be(30);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnableSigning_DefaultsToTrue()
{
var options = new HumanApprovalAttestationOptions();
@@ -557,7 +590,8 @@ public sealed class HumanApprovalAttestationOptionsTests
options.EnableSigning.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MinJustificationLength_DefaultsTo10()
{
var options = new HumanApprovalAttestationOptions();
@@ -565,7 +599,8 @@ public sealed class HumanApprovalAttestationOptionsTests
options.MinJustificationLength.Should().Be(10);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void HighSeverityApproverRoles_HasDefaultRoles()
{
var options = new HumanApprovalAttestationOptions();
@@ -581,7 +616,8 @@ public sealed class HumanApprovalAttestationOptionsTests
/// </summary>
public sealed class HumanApprovalStatementTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Type_AlwaysReturnsInTotoStatementV1()
{
var statement = CreateValidStatement();
@@ -589,7 +625,8 @@ public sealed class HumanApprovalStatementTests
statement.Type.Should().Be("https://in-toto.io/Statement/v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PredicateType_AlwaysReturnsCorrectUri()
{
var statement = CreateValidStatement();
@@ -597,7 +634,8 @@ public sealed class HumanApprovalStatementTests
statement.PredicateType.Should().Be("stella.ops/human-approval@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Schema_AlwaysReturnsHumanApprovalV1()
{
var statement = CreateValidStatement();
@@ -631,7 +669,8 @@ public sealed class HumanApprovalStatementTests
/// </summary>
public sealed class ApprovalDecisionTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ApprovalDecision.AcceptRisk, "AcceptRisk")]
[InlineData(ApprovalDecision.Defer, "Defer")]
[InlineData(ApprovalDecision.Reject, "Reject")]
@@ -648,7 +687,8 @@ public sealed class ApprovalDecisionTests
/// </summary>
public sealed class HumanApprovalAttestationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_CreatesSuccessResult()
{
var statement = CreateValidStatement();
@@ -661,7 +701,8 @@ public sealed class HumanApprovalAttestationResultTests
result.IsRevoked.Should().BeFalse();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_WithDsseEnvelope_IncludesEnvelope()
{
var statement = CreateValidStatement();
@@ -673,7 +714,8 @@ public sealed class HumanApprovalAttestationResultTests
result.DsseEnvelope.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failed_CreatesFailedResult()
{
var result = HumanApprovalAttestationResult.Failed("Test error message");

View File

@@ -11,6 +11,8 @@ using System.Security.Cryptography;
using System.Text;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -30,7 +32,8 @@ public sealed class IdempotencyMiddlewareTests
config["Scanner:Idempotency:Window"] = "24:00:00";
});
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostRequest_WithContentDigest_ReturnsIdempotencyKey()
{
// Arrange
@@ -50,7 +53,8 @@ public sealed class IdempotencyMiddlewareTests
Assert.NotEqual(HttpStatusCode.InternalServerError, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DuplicateRequest_WithSameContentDigest_ReturnsCachedResponse()
{
// Arrange
@@ -75,7 +79,8 @@ public sealed class IdempotencyMiddlewareTests
Assert.NotEqual(HttpStatusCode.InternalServerError, response2.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DifferentRequests_WithDifferentDigests_AreProcessedSeparately()
{
// Arrange
@@ -100,7 +105,8 @@ public sealed class IdempotencyMiddlewareTests
Assert.NotEqual(HttpStatusCode.InternalServerError, response2.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRequest_BypassesIdempotencyMiddleware()
{
// Arrange
@@ -114,7 +120,8 @@ public sealed class IdempotencyMiddlewareTests
Assert.NotEqual(HttpStatusCode.InternalServerError, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostRequest_WithoutContentDigest_ComputesDigest()
{
// Arrange

View File

@@ -7,11 +7,13 @@ using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Services;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class LinksetResolverTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ResolveAsync_MapsSeveritiesAndConflicts()
{
var linkset = new AdvisoryLinkset(
@@ -68,7 +70,8 @@ public sealed class LinksetResolverTests
Assert.Equal("disagree", conflict.Reason);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ResolveAsync_ReturnsEmptyWhenNoIds()
{
var resolver = new LinksetResolver(

View File

@@ -16,6 +16,8 @@ using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -27,7 +29,8 @@ public sealed class ManifestEndpointsTests
#region GET /scans/{scanId}/manifest Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetManifest_ReturnsManifest_WhenExists()
{
// Arrange
@@ -73,7 +76,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal("1.0.0-test", manifest.ScannerVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetManifest_Returns404_WhenNotFound()
{
// Arrange
@@ -88,7 +92,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetManifest_Returns404_WhenInvalidGuid()
{
// Arrange
@@ -102,7 +107,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetManifest_ReturnsDsse_WhenAcceptHeaderRequestsDsse()
{
// Arrange
@@ -161,7 +167,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(scanId, signedManifest.Manifest.ScanId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetManifest_IncludesContentDigest_InPlainResponse()
{
// Arrange
@@ -206,7 +213,8 @@ public sealed class ManifestEndpointsTests
#region GET /scans/{scanId}/proofs Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListProofs_ReturnsEmptyList_WhenNoProofs()
{
// Arrange
@@ -226,7 +234,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(0, proofsResponse.Total);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListProofs_ReturnsProofs_WhenExists()
{
// Arrange
@@ -271,7 +280,8 @@ public sealed class ManifestEndpointsTests
Assert.Contains(proofsResponse.Items, p => p.RootHash == "sha256:root2" && p.BundleType == "extended");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListProofs_Returns404_WhenInvalidGuid()
{
// Arrange
@@ -289,7 +299,8 @@ public sealed class ManifestEndpointsTests
#region GET /scans/{scanId}/proofs/{rootHash} Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetProof_ReturnsProof_WhenExists()
{
// Arrange
@@ -339,7 +350,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal("ed25519", proofResponse.SignatureAlgorithm);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetProof_Returns404_WhenNotFound()
{
// Arrange
@@ -354,7 +366,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetProof_Returns404_WhenRootHashBelongsToDifferentScan()
{
// Arrange
@@ -385,7 +398,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetProof_Returns404_WhenInvalidScanGuid()
{
// Arrange
@@ -399,7 +413,8 @@ public sealed class ManifestEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetProof_WithTrailingSlash_FallsBackToListEndpoint()
{
// Arrange

View File

@@ -8,6 +8,7 @@ using System.Text.Json.Serialization;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Serialization;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -21,7 +22,8 @@ public sealed class NotifierIngestionTests
Converters = { new JsonStringEnumConverter() }
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NotifierMetadata_SerializesCorrectly()
{
var metadata = new NotifierIngestionMetadata
@@ -53,7 +55,8 @@ public sealed class NotifierIngestionTests
Assert.Contains("slack", channels.Select(c => c?.GetValue<string>()));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NotifierMetadata_OmittedWhenNull()
{
var orchestratorEvent = new OrchestratorEvent
@@ -86,7 +89,8 @@ public sealed class NotifierIngestionTests
Assert.Null(node["notifier"]); // Should be omitted when null
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("critical", true, true)]
[InlineData("high", true, false)]
[InlineData("medium", false, false)]
@@ -99,7 +103,8 @@ public sealed class NotifierIngestionTests
Assert.Equal(expectedImmediate, metadata.ImmediateDispatch);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScanStartedEvent_SerializesForNotifier()
{
var orchestratorEvent = new OrchestratorEvent
@@ -148,7 +153,8 @@ public sealed class NotifierIngestionTests
Assert.Equal("container_image", target["type"]?.GetValue<string>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScanFailedEvent_SerializesWithErrorDetails()
{
var orchestratorEvent = new OrchestratorEvent
@@ -215,7 +221,8 @@ public sealed class NotifierIngestionTests
Assert.True(notifier["immediateDispatch"]?.GetValue<bool>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VulnerabilityDetectedEvent_SerializesForNotifier()
{
var orchestratorEvent = new OrchestratorEvent
@@ -286,7 +293,8 @@ public sealed class NotifierIngestionTests
Assert.Equal("reachable", payload["reachability"]?.GetValue<string>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SbomGeneratedEvent_SerializesForNotifier()
{
var orchestratorEvent = new OrchestratorEvent
@@ -337,7 +345,8 @@ public sealed class NotifierIngestionTests
Assert.Equal(127, payload["componentCount"]?.GetValue<int>());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AllEventKinds_HaveCorrectFormat()
{
Assert.Matches(@"^scanner\.event\.[a-z]+\.[a-z]+$", OrchestratorEventKinds.ScannerReportReady);
@@ -348,7 +357,8 @@ public sealed class NotifierIngestionTests
Assert.Matches(@"^scanner\.event\.[a-z]+\.[a-z]+$", OrchestratorEventKinds.ScannerVulnerabilityDetected);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NotifierChannels_SupportAllChannelTypes()
{
var validChannels = new[] { "email", "slack", "teams", "webhook", "pagerduty" };

View File

@@ -10,11 +10,14 @@ using StellaOps.Authority.Storage.Postgres.Models;
using StellaOps.Authority.Storage.Postgres.Repositories;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class OfflineKitEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OfflineKitImport_ThenStatusAndMetrics_Succeeds()
{
using var contentRoot = new TempDirectory();
@@ -74,7 +77,8 @@ public sealed class OfflineKitEndpointsTests
Assert.Contains("offlinekit_import_total", metrics, StringComparison.Ordinal);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OfflineKitImport_WhenDsseInvalid_ReturnsProblemDetails()
{
using var contentRoot = new TempDirectory();
@@ -131,7 +135,8 @@ public sealed class OfflineKitEndpointsTests
Assert.Equal("DSSE_VERIFY_FAIL", problem.RootElement.GetProperty("extensions").GetProperty("reason_code").GetString());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OfflineKitImport_WhenRequireDsseFalse_AllowsSoftFail()
{
using var contentRoot = new TempDirectory();
@@ -176,7 +181,8 @@ public sealed class OfflineKitEndpointsTests
Assert.Equal(HttpStatusCode.Accepted, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task OfflineKitImport_EmitsAuditEvent_WithTenantHeader()
{
using var contentRoot = new TempDirectory();

View File

@@ -3,11 +3,14 @@ using Microsoft.Extensions.Options;
using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.WebService.Services;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class PlatformEventPublisherRegistrationTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NullPublisherRegisteredWhenEventsDisabled()
{
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
@@ -21,7 +24,8 @@ public sealed class PlatformEventPublisherRegistrationTests
Assert.IsType<NullPlatformEventPublisher>(publisher);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RedisPublisherRegisteredWhenEventsEnabled()
{
var originalEnabled = Environment.GetEnvironmentVariable("SCANNER__EVENTS__ENABLED");

View File

@@ -10,6 +10,7 @@ using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Serialization;
using Xunit.Sdk;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class PlatformEventSamplesTests
@@ -20,7 +21,8 @@ public sealed class PlatformEventSamplesTests
Converters = { new JsonStringEnumConverter() }
};
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("scanner.event.report.ready@1.sample.json", OrchestratorEventKinds.ScannerReportReady)]
[InlineData("scanner.event.scan.completed@1.sample.json", OrchestratorEventKinds.ScannerScanCompleted)]
public void PlatformEventSamplesStayCanonical(string fileName, string expectedKind)

View File

@@ -18,6 +18,7 @@ using Xunit;
using MsOptions = Microsoft.Extensions.Options;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -39,7 +40,8 @@ public sealed class PolicyDecisionAttestationServiceTests
#region CreateAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_ReturnsSuccessResult()
{
// Arrange
@@ -56,7 +58,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_CreatesInTotoStatement()
{
// Arrange
@@ -71,7 +74,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement.PredicateType.Should().Be("stella.ops/policy-decision@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesSubjects()
{
// Arrange
@@ -88,7 +92,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement.Subject[1].Digest.Should().ContainKey("sha256");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesPredicateWithAllFields()
{
// Arrange
@@ -107,7 +112,8 @@ public sealed class PolicyDecisionAttestationServiceTests
predicate.PolicyVersion.Should().Be(input.PolicyVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_SetsEvaluatedAtToCurrentTime()
{
// Arrange
@@ -121,7 +127,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement!.Predicate.EvaluatedAt.Should().Be(expectedTime);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_WithDefaultTtl_SetsExpiresAtTo30Days()
{
// Arrange
@@ -135,7 +142,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_WithCustomTtl_SetsExpiresAtToCustomValue()
{
// Arrange
@@ -149,7 +157,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_IncludesReasoningDetails()
{
// Arrange
@@ -166,7 +175,8 @@ public sealed class PolicyDecisionAttestationServiceTests
reasoning.RiskMultiplier.Should().Be(input.Reasoning.RiskMultiplier);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_GeneratesDeterministicAttestationId()
{
// Arrange
@@ -180,7 +190,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result1.AttestationId.Should().Be(result2.AttestationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_DifferentInputs_GenerateDifferentAttestationIds()
{
// Arrange
@@ -195,7 +206,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result1.AttestationId.Should().NotBe(result2.AttestationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_NullInput_ThrowsArgumentNullException()
{
// Act & Assert
@@ -203,7 +215,8 @@ public sealed class PolicyDecisionAttestationServiceTests
_service.CreateAttestationAsync(null!));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyFindingId_ThrowsArgumentException(string findingId)
@@ -216,7 +229,8 @@ public sealed class PolicyDecisionAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyCve_ThrowsArgumentException(string cve)
@@ -229,7 +243,8 @@ public sealed class PolicyDecisionAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyComponentPurl_ThrowsArgumentException(string purl)
@@ -246,7 +261,8 @@ public sealed class PolicyDecisionAttestationServiceTests
#region GetAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_ExistingAttestation_ReturnsAttestation()
{
// Arrange
@@ -262,7 +278,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Statement!.Predicate.FindingId.Should().Be(input.FindingId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_NonExistentAttestation_ReturnsNull()
{
// Act
@@ -274,7 +291,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_WrongScanId_ReturnsNull()
{
// Arrange
@@ -290,7 +308,8 @@ public sealed class PolicyDecisionAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_WrongFindingId_ReturnsNull()
{
// Arrange
@@ -310,7 +329,8 @@ public sealed class PolicyDecisionAttestationServiceTests
#region Decision Type Tests
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(PolicyDecision.Allow)]
[InlineData(PolicyDecision.Review)]
[InlineData(PolicyDecision.Block)]
@@ -333,7 +353,8 @@ public sealed class PolicyDecisionAttestationServiceTests
#region Serialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_SerializesToValidJson()
{
// Arrange
@@ -350,7 +371,8 @@ public sealed class PolicyDecisionAttestationServiceTests
json.Should().Contain("\"predicate\":");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_PredicateType_IsCorrectUri()
{
// Arrange
@@ -417,7 +439,8 @@ public sealed class PolicyDecisionAttestationServiceTests
/// </summary>
public sealed class PolicyDecisionAttestationOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultDecisionTtlDays_DefaultsToThirtyDays()
{
var options = new PolicyDecisionAttestationOptions();
@@ -425,7 +448,8 @@ public sealed class PolicyDecisionAttestationOptionsTests
options.DefaultDecisionTtlDays.Should().Be(30);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnableSigning_DefaultsToTrue()
{
var options = new PolicyDecisionAttestationOptions();
@@ -433,7 +457,8 @@ public sealed class PolicyDecisionAttestationOptionsTests
options.EnableSigning.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_CanBeConfigured()
{
var options = new PolicyDecisionAttestationOptions
@@ -452,7 +477,8 @@ public sealed class PolicyDecisionAttestationOptionsTests
/// </summary>
public sealed class PolicyDecisionStatementTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Type_AlwaysReturnsInTotoStatementV1()
{
var statement = CreateValidStatement();
@@ -460,7 +486,8 @@ public sealed class PolicyDecisionStatementTests
statement.Type.Should().Be("https://in-toto.io/Statement/v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PredicateType_AlwaysReturnsCorrectUri()
{
var statement = CreateValidStatement();
@@ -468,7 +495,8 @@ public sealed class PolicyDecisionStatementTests
statement.PredicateType.Should().Be("stella.ops/policy-decision@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Subject_CanContainMultipleEntries()
{
var statement = CreateValidStatement();
@@ -511,7 +539,8 @@ public sealed class PolicyDecisionStatementTests
/// </summary>
public sealed class PolicyDecisionReasoningTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Reasoning_RequiredFieldsAreSet()
{
var reasoning = new PolicyDecisionReasoning
@@ -528,7 +557,8 @@ public sealed class PolicyDecisionReasoningTests
reasoning.RiskMultiplier.Should().Be(0.8);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Reasoning_OptionalFieldsCanBeNull()
{
var reasoning = new PolicyDecisionReasoning
@@ -544,7 +574,8 @@ public sealed class PolicyDecisionReasoningTests
reasoning.Summary.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Reasoning_OptionalFieldsCanBeSet()
{
var reasoning = new PolicyDecisionReasoning
@@ -569,7 +600,8 @@ public sealed class PolicyDecisionReasoningTests
/// </summary>
public sealed class PolicyDecisionAttestationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_CreatesSuccessResult()
{
var statement = CreateValidStatement();
@@ -581,7 +613,8 @@ public sealed class PolicyDecisionAttestationResultTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_WithDsseEnvelope_IncludesEnvelope()
{
var statement = CreateValidStatement();
@@ -593,7 +626,8 @@ public sealed class PolicyDecisionAttestationResultTests
result.DsseEnvelope.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failed_CreatesFailedResult()
{
var result = PolicyDecisionAttestationResult.Failed("Test error message");

View File

@@ -5,13 +5,16 @@ using System.Threading.Tasks;
using StellaOps.Policy;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class PolicyEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PolicySchemaReturnsEmbeddedSchema()
{
using var factory = new ScannerApplicationFactory();
@@ -26,7 +29,8 @@ public sealed class PolicyEndpointsTests
Assert.Contains("\"properties\"", payload);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PolicyDiagnosticsReturnsRecommendations()
{
using var factory = new ScannerApplicationFactory();
@@ -53,7 +57,8 @@ public sealed class PolicyEndpointsTests
Assert.NotEmpty(diagnostics.Recommendations);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PolicyPreviewUsesProposedPolicy()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -9,13 +9,16 @@ using Microsoft.Extensions.DependencyInjection;
using StellaOps.Scanner.ProofSpine;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ProofSpineEndpointsTests
{
private const string CborContentType = "application/cbor";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSpine_ReturnsSpine_WithVerification()
{
await using var factory = new ScannerApplicationFactory();
@@ -54,7 +57,8 @@ public sealed class ProofSpineEndpointsTests
Assert.True(body.TryGetProperty("verification", out _));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSpine_ReturnsCbor_WhenAcceptHeaderRequestsCbor()
{
await using var factory = new ScannerApplicationFactory();
@@ -90,7 +94,8 @@ public sealed class ProofSpineEndpointsTests
Assert.True(((List<object?>)decoded["segments"]!).Count > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListSpinesByScan_ReturnsSummaries_WithSegmentCount()
{
await using var factory = new ScannerApplicationFactory();
@@ -128,7 +133,8 @@ public sealed class ProofSpineEndpointsTests
Assert.True(items[0].GetProperty("segmentCount").GetInt32() > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ListSpinesByScan_ReturnsCbor_WhenAcceptHeaderRequestsCbor()
{
await using var factory = new ScannerApplicationFactory();
@@ -166,7 +172,8 @@ public sealed class ProofSpineEndpointsTests
Assert.True((int)first["segmentCount"]! > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSpine_ReturnsInvalidStatus_WhenSegmentTampered()
{
await using var factory = new ScannerApplicationFactory();

View File

@@ -9,6 +9,8 @@ using System.Net;
using System.Net.Http.Headers;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -32,7 +34,8 @@ public sealed class RateLimitingTests
config["scanner:rateLimiting:proofBundleWindow"] = TimeSpan.FromSeconds(windowSeconds).ToString();
});
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ManifestEndpoint_IncludesRateLimitHeaders()
{
// Arrange
@@ -50,7 +53,8 @@ public sealed class RateLimitingTests
response.StatusCode == HttpStatusCode.TooManyRequests);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ProofBundleEndpoint_IncludesRateLimitHeaders()
{
// Arrange
@@ -67,7 +71,8 @@ public sealed class RateLimitingTests
response.StatusCode == HttpStatusCode.TooManyRequests);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ExcessiveRequests_Returns429()
{
// Arrange - Create factory with very low rate limit for testing
@@ -93,7 +98,8 @@ public sealed class RateLimitingTests
"Expected either rate limiting (429) or successful responses (200/404)");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RateLimited_Returns429WithRetryAfter()
{
// Arrange
@@ -115,7 +121,8 @@ public sealed class RateLimitingTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HealthEndpoint_NotRateLimited()
{
// Arrange
@@ -134,7 +141,8 @@ public sealed class RateLimitingTests
Assert.All(responses, r => Assert.NotEqual(HttpStatusCode.TooManyRequests, r.StatusCode));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RateLimitedResponse_HasProblemDetails()
{
// Arrange
@@ -157,7 +165,8 @@ public sealed class RateLimitingTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DifferentTenants_HaveSeparateRateLimits()
{
// This test verifies tenant isolation in rate limiting

View File

@@ -10,11 +10,14 @@ using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ReachabilityDriftEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDriftReturnsNotFoundWhenNoResultAndNoBaseScanProvided()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -32,7 +35,8 @@ public sealed class ReachabilityDriftEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetDriftComputesResultAndListsDriftedSinks()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -10,6 +10,7 @@ using StellaOps.Scanner.WebService.Contracts;
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -21,7 +22,8 @@ public sealed class ReplayCommandServiceTests
{
#region RCG-9200-025: ReplayCommandService - All Command Formats
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandDto_FullCommand_ContainsAllParameters()
{
// Arrange
@@ -61,7 +63,8 @@ public sealed class ReplayCommandServiceTests
dto.RequiresNetwork.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandDto_ShortCommand_UsesSnapshotReference()
{
// Arrange
@@ -92,7 +95,8 @@ public sealed class ReplayCommandServiceTests
dto.Command.Should().NotContain("--policy-hash");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandDto_OfflineCommand_HasOfflineFlag()
{
// Arrange
@@ -129,7 +133,8 @@ public sealed class ReplayCommandServiceTests
dto.Prerequisites.Should().Contain(p => p.Contains("bundle"));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("bash")]
[InlineData("powershell")]
[InlineData("cmd")]
@@ -154,7 +159,8 @@ public sealed class ReplayCommandServiceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandPartsDto_HasStructuredBreakdown()
{
// Arrange
@@ -181,7 +187,8 @@ public sealed class ReplayCommandServiceTests
parts.Flags.Should().HaveCount(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandResponseDto_ContainsAllCommandVariants()
{
// Arrange
@@ -196,7 +203,8 @@ public sealed class ReplayCommandServiceTests
response.OfflineCommand!.Type.Should().Be("offline");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ScanReplayCommandResponseDto_ContainsExpectedFields()
{
// Arrange
@@ -224,7 +232,8 @@ public sealed class ReplayCommandServiceTests
#region RCG-9200-026: Evidence Bundle Generation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceBundleInfoDto_ContainsRequiredFields()
{
// Arrange
@@ -254,7 +263,8 @@ public sealed class ReplayCommandServiceTests
bundle.Contents.Should().Contain("manifest.json");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("tar.gz")]
[InlineData("zip")]
public void EvidenceBundleInfoDto_SupportsBothFormats(string format)
@@ -273,7 +283,8 @@ public sealed class ReplayCommandServiceTests
bundle.DownloadUri.Should().EndWith(format);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceBundleInfoDto_HasExpirationDate()
{
// Arrange
@@ -291,7 +302,8 @@ public sealed class ReplayCommandServiceTests
bundle.ExpiresAt.Should().BeBefore(now.AddDays(30));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceBundleInfoDto_ContainsExpectedManifestItems()
{
// Arrange
@@ -328,7 +340,8 @@ public sealed class ReplayCommandServiceTests
#region RCG-9200-027/028: Integration Test Stubs (Unit Test Versions)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GenerateReplayCommandRequestDto_HasRequiredFields()
{
// Arrange
@@ -348,7 +361,8 @@ public sealed class ReplayCommandServiceTests
request.GenerateBundle.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GenerateScanReplayCommandRequestDto_HasRequiredFields()
{
// Arrange
@@ -366,7 +380,8 @@ public sealed class ReplayCommandServiceTests
request.GenerateBundle.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandResponseDto_FindingAndScanIds_ArePopulated()
{
// Arrange
@@ -395,7 +410,8 @@ public sealed class ReplayCommandServiceTests
#region RCG-9200-029: Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ExpectedVerdictHash_IsDeterministic()
{
// Arrange
@@ -421,7 +437,8 @@ public sealed class ReplayCommandServiceTests
response1.ExpectedVerdictHash.Should().Be(response2.ExpectedVerdictHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SnapshotInfoDto_EnablesDeterministicReplay()
{
// Arrange
@@ -446,7 +463,8 @@ public sealed class ReplayCommandServiceTests
snapshot.ContentHash.Should().StartWith("sha256:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CommandParts_CanBeReassembledDeterministically()
{
// Arrange
@@ -474,7 +492,8 @@ public sealed class ReplayCommandServiceTests
reassembled.Should().Contain("--verify");
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("pkg:npm/lodash@4.17.21", "CVE-2024-0001", "sha256:feed123", "sha256:policy456")]
[InlineData("pkg:maven/org.example/lib@1.0.0", "CVE-2023-9999", "sha256:feedabc", "sha256:policydef")]
public void FullCommand_IncludesAllDeterminismInputs(
@@ -510,7 +529,8 @@ public sealed class ReplayCommandServiceTests
dto.Parts!.Arguments.Should().HaveCount(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void OfflineBundle_ContainsSameInputsAsOnlineReplay()
{
// Arrange
@@ -541,7 +561,8 @@ public sealed class ReplayCommandServiceTests
#region JSON Serialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandResponseDto_Serializes_Correctly()
{
// Arrange
@@ -558,7 +579,8 @@ public sealed class ReplayCommandServiceTests
deserialized.Snapshot.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommandDto_HasExpectedJsonStructure()
{
// Arrange
@@ -574,7 +596,8 @@ public sealed class ReplayCommandServiceTests
json.Should().Contain("\"RequiresNetwork\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SnapshotInfoDto_Serializes_WithFeedVersions()
{
// Arrange

View File

@@ -19,6 +19,7 @@ using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.WebService.Services;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ReportEventDispatcherTests
@@ -28,7 +29,8 @@ public sealed class ReportEventDispatcherTests
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishAsync_EmitsReportReadyAndScanCompleted()
{
var publisher = new RecordingEventPublisher();
@@ -168,7 +170,8 @@ public sealed class ReportEventDispatcherTests
Assert.Equal("blocked", scanPayload.Report.Verdict);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishAsync_RecordsFnDriftClassificationChanges()
{
var publisher = new RecordingEventPublisher();
@@ -249,7 +252,8 @@ public sealed class ReportEventDispatcherTests
Assert.NotEqual(Guid.Empty, change.ManifestId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishAsync_DoesNotFailWhenFnDriftTrackingThrows()
{
var publisher = new RecordingEventPublisher();
@@ -305,7 +309,8 @@ public sealed class ReportEventDispatcherTests
Assert.Equal(2, publisher.Events.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishAsync_HonoursConfiguredConsoleAndApiSegments()
{
var options = Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions

View File

@@ -5,6 +5,8 @@ using System.Text.Json.Serialization;
using System.Threading.Tasks;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ReportSamplesTests
@@ -15,7 +17,8 @@ public sealed class ReportSamplesTests
Converters = { new JsonStringEnumConverter() }
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportSampleEnvelope_RemainsCanonical()
{
var baseDirectory = AppContext.BaseDirectory;

View File

@@ -13,6 +13,8 @@ using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Services;
using System.Linq;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ReportsEndpointsTests
@@ -22,7 +24,8 @@ public sealed class ReportsEndpointsTests
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsEndpointReturnsSignedEnvelope()
{
const string policyYaml = """
@@ -102,7 +105,8 @@ rules:
Assert.True(expectedSig == actualSig, $"expected:{expectedSig}, actual:{actualSig}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsEndpointValidatesDigest()
{
using var factory = new ScannerApplicationFactory();
@@ -118,7 +122,8 @@ rules:
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsEndpointReturnsServiceUnavailableWhenPolicyMissing()
{
using var factory = new ScannerApplicationFactory();
@@ -137,7 +142,8 @@ rules:
Assert.Equal((HttpStatusCode)StatusCodes.Status503ServiceUnavailable, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReportsEndpointPublishesPlatformEvents()
{
const string policyYaml = """

View File

@@ -18,6 +18,7 @@ using Xunit;
using MsOptions = Microsoft.Extensions.Options;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -39,7 +40,8 @@ public sealed class RichGraphAttestationServiceTests
#region CreateAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_ReturnsSuccessResult()
{
// Arrange
@@ -56,7 +58,8 @@ public sealed class RichGraphAttestationServiceTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_CreatesInTotoStatement()
{
// Arrange
@@ -71,7 +74,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement.PredicateType.Should().Be("stella.ops/richgraph@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesSubjects()
{
// Arrange
@@ -88,7 +92,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement.Subject[1].Digest.Should().ContainKey("sha256");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesPredicateWithGraphMetrics()
{
// Arrange
@@ -106,7 +111,8 @@ public sealed class RichGraphAttestationServiceTests
predicate.RootCount.Should().Be(input.RootCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_IncludesAnalyzerInfo()
{
// Arrange
@@ -122,7 +128,8 @@ public sealed class RichGraphAttestationServiceTests
analyzer.ConfigHash.Should().Be(input.AnalyzerConfigHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_ValidInput_SetsComputedAtToCurrentTime()
{
// Arrange
@@ -136,7 +143,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement!.Predicate.ComputedAt.Should().Be(expectedTime);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_WithDefaultTtl_SetsExpiresAtTo7Days()
{
// Arrange
@@ -150,7 +158,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_WithCustomTtl_SetsExpiresAtToCustomValue()
{
// Arrange
@@ -164,7 +173,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement!.Predicate.ExpiresAt.Should().Be(expectedExpiry);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_IncludesOptionalRefs()
{
// Arrange
@@ -184,7 +194,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement.Predicate.Language.Should().Be("java");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_GeneratesDeterministicAttestationId()
{
// Arrange
@@ -198,7 +209,8 @@ public sealed class RichGraphAttestationServiceTests
result1.AttestationId.Should().Be(result2.AttestationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_DifferentInputs_GenerateDifferentAttestationIds()
{
// Arrange
@@ -213,7 +225,8 @@ public sealed class RichGraphAttestationServiceTests
result1.AttestationId.Should().NotBe(result2.AttestationId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task CreateAttestationAsync_NullInput_ThrowsArgumentNullException()
{
// Act & Assert
@@ -221,7 +234,8 @@ public sealed class RichGraphAttestationServiceTests
_service.CreateAttestationAsync(null!));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyGraphId_ThrowsArgumentException(string graphId)
@@ -234,7 +248,8 @@ public sealed class RichGraphAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyGraphDigest_ThrowsArgumentException(string graphDigest)
@@ -247,7 +262,8 @@ public sealed class RichGraphAttestationServiceTests
_service.CreateAttestationAsync(input));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task CreateAttestationAsync_EmptyAnalyzerName_ThrowsArgumentException(string analyzerName)
@@ -264,7 +280,8 @@ public sealed class RichGraphAttestationServiceTests
#region GetAttestationAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_ExistingAttestation_ReturnsAttestation()
{
// Arrange
@@ -280,7 +297,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement!.Predicate.GraphId.Should().Be(input.GraphId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_NonExistentAttestation_ReturnsNull()
{
// Act
@@ -290,7 +308,8 @@ public sealed class RichGraphAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_WrongScanId_ReturnsNull()
{
// Arrange
@@ -304,7 +323,8 @@ public sealed class RichGraphAttestationServiceTests
result.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetAttestationAsync_WrongGraphId_ReturnsNull()
{
// Arrange
@@ -322,7 +342,8 @@ public sealed class RichGraphAttestationServiceTests
#region Serialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_SerializesToValidJson()
{
// Arrange
@@ -339,7 +360,8 @@ public sealed class RichGraphAttestationServiceTests
json.Should().Contain("\"predicate\":");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_PredicateType_IsCorrectUri()
{
// Arrange
@@ -352,7 +374,8 @@ public sealed class RichGraphAttestationServiceTests
result.Statement!.PredicateType.Should().Be("stella.ops/richgraph@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Statement_Schema_IsRichGraphV1()
{
// Arrange
@@ -409,7 +432,8 @@ public sealed class RichGraphAttestationServiceTests
/// </summary>
public sealed class RichGraphAttestationOptionsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DefaultGraphTtlDays_DefaultsToSevenDays()
{
var options = new RichGraphAttestationOptions();
@@ -417,7 +441,8 @@ public sealed class RichGraphAttestationOptionsTests
options.DefaultGraphTtlDays.Should().Be(7);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EnableSigning_DefaultsToTrue()
{
var options = new RichGraphAttestationOptions();
@@ -425,7 +450,8 @@ public sealed class RichGraphAttestationOptionsTests
options.EnableSigning.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Options_CanBeConfigured()
{
var options = new RichGraphAttestationOptions
@@ -444,7 +470,8 @@ public sealed class RichGraphAttestationOptionsTests
/// </summary>
public sealed class RichGraphStatementTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Type_AlwaysReturnsInTotoStatementV1()
{
var statement = CreateValidStatement();
@@ -452,7 +479,8 @@ public sealed class RichGraphStatementTests
statement.Type.Should().Be("https://in-toto.io/Statement/v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PredicateType_AlwaysReturnsCorrectUri()
{
var statement = CreateValidStatement();
@@ -460,7 +488,8 @@ public sealed class RichGraphStatementTests
statement.PredicateType.Should().Be("stella.ops/richgraph@v1");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Subject_CanContainMultipleEntries()
{
var statement = CreateValidStatement();
@@ -500,7 +529,8 @@ public sealed class RichGraphStatementTests
/// </summary>
public sealed class RichGraphAttestationResultTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_CreatesSuccessResult()
{
var statement = CreateValidStatement();
@@ -512,7 +542,8 @@ public sealed class RichGraphAttestationResultTests
result.Error.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Succeeded_WithDsseEnvelope_IncludesEnvelope()
{
var statement = CreateValidStatement();
@@ -524,7 +555,8 @@ public sealed class RichGraphAttestationResultTests
result.DsseEnvelope.Should().NotBeNullOrEmpty();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Failed_CreatesFailedResult()
{
var result = RichGraphAttestationResult.Failed("Test error message");

View File

@@ -18,11 +18,14 @@ using StellaOps.Scanner.WebService.Domain;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class RubyPackagesEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRubyPackagesReturnsNotFoundWhenInventoryMissing()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -34,7 +37,8 @@ public sealed class RubyPackagesEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRubyPackagesReturnsInventory()
{
const string scanId = "scan-ruby-existing";
@@ -84,7 +88,8 @@ public sealed class RubyPackagesEndpointsTests
Assert.Equal("rubygems", payload.Packages[0].Source);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRubyPackagesAllowsDigestIdentifier()
{
const string reference = "ghcr.io/demo/ruby-service:1.2.3";
@@ -143,7 +148,8 @@ public sealed class RubyPackagesEndpointsTests
Assert.Equal("rails", payload.Packages[0].Name);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRubyPackagesAllowsReferenceIdentifier()
{
const string reference = "ghcr.io/demo/ruby-service:latest";
@@ -200,7 +206,8 @@ public sealed class RubyPackagesEndpointsTests
Assert.Equal("sidekiq", payload.Packages[0].Name);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEntryTraceAllowsDigestIdentifier()
{
const string reference = "ghcr.io/demo/app:2.0.0";

View File

@@ -12,11 +12,14 @@ using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Zastava.Core.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class RuntimeEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimeEventsEndpointPersistsEvents()
{
using var factory = new ScannerApplicationFactory();
@@ -54,7 +57,8 @@ public sealed class RuntimeEndpointsTests
});
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimeEventsEndpointRejectsUnsupportedSchema()
{
using var factory = new ScannerApplicationFactory();
@@ -71,7 +75,8 @@ public sealed class RuntimeEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimeEventsEndpointEnforcesRateLimit()
{
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
@@ -102,7 +107,8 @@ public sealed class RuntimeEndpointsTests
Assert.Equal(0, count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimePolicyEndpointReturnsDecisions()
{
using var factory = new ScannerApplicationFactory().WithOverrides(configuration =>
@@ -231,7 +237,8 @@ rules:
Assert.True(metadataDocument.RootElement.TryGetProperty("heuristics", out _));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimePolicyEndpointFlagsUnsignedAndMissingSbom()
{
using var factory = new ScannerApplicationFactory();
@@ -287,7 +294,8 @@ rules: []
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RuntimePolicyEndpointValidatesRequest()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -15,6 +15,8 @@ using StellaOps.Scanner.Storage.Repositories;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Zastava.Core.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class RuntimeReconciliationTests
@@ -23,7 +25,8 @@ public sealed class RuntimeReconciliationTests
private const string TestTenant = "tenant-alpha";
private const string TestNode = "node-a";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithNoRuntimeEvents_ReturnsNotFound()
{
using var factory = new ScannerApplicationFactory();
@@ -44,7 +47,8 @@ public sealed class RuntimeReconciliationTests
Assert.Contains("No runtime events found", payload.ErrorMessage);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithRuntimeEventsButNoSbom_ReturnsNoSbomError()
{
var mockObjectStore = new InMemoryArtifactObjectStore();
@@ -93,7 +97,8 @@ public sealed class RuntimeReconciliationTests
Assert.Equal(2, payload.Misses.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithHashMatches_ReturnsMatches()
{
var mockObjectStore = new InMemoryArtifactObjectStore();
@@ -183,7 +188,8 @@ public sealed class RuntimeReconciliationTests
Assert.All(payload.Matches, m => Assert.Equal("sha256", m.MatchType));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithPathMatches_ReturnsMatches()
{
var mockObjectStore = new InMemoryArtifactObjectStore();
@@ -268,7 +274,8 @@ public sealed class RuntimeReconciliationTests
Assert.Equal("path", payload.Matches[0].MatchType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithSpecificEventId_UsesSpecifiedEvent()
{
var mockObjectStore = new InMemoryArtifactObjectStore();
@@ -356,7 +363,8 @@ public sealed class RuntimeReconciliationTests
Assert.Equal(0, payload.MissCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithNonExistentEventId_ReturnsNotFound()
{
using var factory = new ScannerApplicationFactory();
@@ -377,7 +385,8 @@ public sealed class RuntimeReconciliationTests
Assert.Equal("RUNTIME_EVENT_NOT_FOUND", payload!.ErrorCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithMissingImageDigest_ReturnsBadRequest()
{
using var factory = new ScannerApplicationFactory();
@@ -393,7 +402,8 @@ public sealed class RuntimeReconciliationTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReconcileEndpoint_WithMixedMatchesAndMisses_ReturnsCorrectCounts()
{
var mockObjectStore = new InMemoryArtifactObjectStore();

View File

@@ -8,11 +8,14 @@ using StellaOps.Scanner.Storage.ObjectStore;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class SbomEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitSbomAcceptsCycloneDxJson()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -7,11 +7,14 @@ using StellaOps.Scanner.Storage.ObjectStore;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class SbomUploadEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_accepts_cyclonedx_fixture_and_returns_record()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -56,7 +59,8 @@ public sealed class SbomUploadEndpointsTests
Assert.Equal("build-123", record.Source?.CiContext?.BuildId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_accepts_spdx_fixture_and_reports_quality_score()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -81,7 +85,8 @@ public sealed class SbomUploadEndpointsTests
Assert.True(payload.ValidationResult.ComponentCount > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Upload_rejects_unknown_format()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -11,11 +11,14 @@ using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.Storage;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class ScannerSurfaceSecretConfiguratorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Configure_AppliesCasAccessSecretToArtifactStore()
{
const string json = """
@@ -52,7 +55,8 @@ public sealed class ScannerSurfaceSecretConfiguratorTests
Assert.Equal("ap-southeast-2", options.ArtifactStore.Region);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PostConfigure_SynchronizesArtifactStoreToScannerStorageOptions()
{
var webOptions = Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions
@@ -85,7 +89,8 @@ public sealed class ScannerSurfaceSecretConfiguratorTests
Assert.Equal("X-Sync", storageOptions.ObjectStore.RustFs.ApiKeyHeader);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Configure_AppliesAttestationSecretToSigning()
{
const string json = """
@@ -116,7 +121,8 @@ public sealed class ScannerSurfaceSecretConfiguratorTests
Assert.Equal("CHAIN-PEM", options.Signing.CertificateChainPem);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Configure_AppliesRegistrySecretToOptions()
{
const string json = """

View File

@@ -5,11 +5,14 @@ using System.Threading.Tasks;
using StellaOps.Scanner.WebService.Contracts;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed partial class ScansEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EntropyEndpoint_AttachesSnapshot_AndSurfacesInStatus()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -14,11 +14,14 @@ using StellaOps.Scanner.WebService.Replay;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed partial class ScansEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecordModeService_StoresBundlesAndAttachesReplay()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -10,11 +10,14 @@ using StellaOps.Scanner.WebService.Replay;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed partial class ScansEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecordModeService_AttachesReplayAndSurfacedInStatus()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -14,11 +14,14 @@ using StellaOps.Scanner.WebService.Domain;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed partial class ScansEndpointsTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitScanValidatesImageDescriptor()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -33,7 +36,8 @@ public sealed partial class ScansEndpointsTests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitScanPropagatesRequestAbortedToken()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -72,7 +76,8 @@ public sealed partial class ScansEndpointsTests
Assert.True(coordinator.LastToken.CanBeCanceled);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SubmitScanAddsDeterminismPinsToMetadata()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -110,7 +115,8 @@ public sealed partial class ScansEndpointsTests
Assert.Equal("rev-42", metadata["determinism.policy"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEntryTraceReturnsStoredResult()
{
using var secrets = new TestSurfaceSecretsScope();
@@ -165,7 +171,8 @@ public sealed partial class ScansEndpointsTests
Assert.Equal(storedResult.Graph.Plans.Length, payload.Graph.Plans.Length);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetEntryTraceReturnsNotFoundWhenMissing()
{
using var secrets = new TestSurfaceSecretsScope();

View File

@@ -8,6 +8,8 @@ using StellaOps.Scanner.WebService.Endpoints;
using StellaOps.Scanner.WebService.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -24,7 +26,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
_client = fixture.Factory.CreateClient();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QuerySlice_WithValidCve_ReturnsSlice()
{
// Arrange
@@ -47,7 +50,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Unexpected status: {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QuerySlice_WithoutScanId_ReturnsBadRequest()
{
// Arrange
@@ -66,7 +70,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Expected BadRequest or Unauthorized, got {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task QuerySlice_WithoutCveOrSymbols_ReturnsBadRequest()
{
// Arrange
@@ -85,7 +90,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Expected BadRequest or Unauthorized, got {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSlice_WithValidDigest_ReturnsSlice()
{
// Arrange
@@ -102,7 +108,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Unexpected status: {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSlice_WithDsseAccept_ReturnsDsseEnvelope()
{
// Arrange
@@ -121,7 +128,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Unexpected status: {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReplaySlice_WithValidDigest_ReturnsReplayResult()
{
// Arrange
@@ -141,7 +149,8 @@ public sealed class SliceEndpointsTests : IClassFixture<ScannerApplicationFixtur
$"Unexpected status: {response.StatusCode}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ReplaySlice_WithoutDigest_ReturnsBadRequest()
{
// Arrange
@@ -165,7 +174,8 @@ public sealed class SliceDiffComputerTests
{
private readonly SliceDiffComputer _computer = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compare_IdenticalSlices_ReturnsMatch()
{
// Arrange
@@ -183,7 +193,8 @@ public sealed class SliceDiffComputerTests
Assert.Null(result.VerdictDiff);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compare_DifferentNodes_ReturnsDiff()
{
// Arrange
@@ -211,7 +222,8 @@ public sealed class SliceDiffComputerTests
Assert.Contains("extra-node", result.ExtraNodes);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compare_DifferentEdges_ReturnsDiff()
{
// Arrange
@@ -232,7 +244,8 @@ public sealed class SliceDiffComputerTests
Assert.Single(result.MissingEdges);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compare_DifferentVerdict_ReturnsDiff()
{
// Arrange
@@ -254,7 +267,8 @@ public sealed class SliceDiffComputerTests
Assert.Contains("Status:", result.VerdictDiff);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeCacheKey_SameInputs_ReturnsSameKey()
{
// Arrange
@@ -269,7 +283,8 @@ public sealed class SliceDiffComputerTests
Assert.Equal(key1, key2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeCacheKey_DifferentInputs_ReturnsDifferentKey()
{
// Arrange
@@ -283,7 +298,8 @@ public sealed class SliceDiffComputerTests
Assert.NotEqual(key1, key2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ToSummary_MatchingSlices_ReturnsMatchMessage()
{
// Arrange
@@ -296,7 +312,8 @@ public sealed class SliceDiffComputerTests
Assert.Contains("match exactly", summary);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ToSummary_DifferingSlices_ReturnsDetailedDiff()
{
// Arrange
@@ -360,7 +377,8 @@ public sealed class SliceDiffComputerTests
/// </summary>
public sealed class SliceCacheTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TryGetAsync_EmptyCache_ReturnsNull()
{
// Arrange
@@ -374,7 +392,8 @@ public sealed class SliceCacheTests
Assert.Null(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SetAsync_ThenTryGetAsync_ReturnsEntry()
{
// Arrange
@@ -391,7 +410,8 @@ public sealed class SliceCacheTests
Assert.Equal("sha256:abc123", result!.SliceDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TryGetAsync_IncrementsCacheStats()
{
// Arrange
@@ -412,7 +432,8 @@ public sealed class SliceCacheTests
Assert.Equal(0.5, stats.HitRate, 2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ClearAsync_RemovesAllEntries()
{
// Arrange
@@ -430,7 +451,8 @@ public sealed class SliceCacheTests
Assert.Equal(0, stats.EntryCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RemoveAsync_RemovesSpecificEntry()
{
// Arrange
@@ -448,7 +470,8 @@ public sealed class SliceCacheTests
Assert.NotNull(await cache.TryGetAsync("key2"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Disabled_NeverCaches()
{
// Arrange

View File

@@ -6,11 +6,13 @@ using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.FS;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class SurfaceCacheOptionsConfiguratorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Configure_UsesSurfaceEnvironmentCacheRoot()
{
var cacheRoot = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));

View File

@@ -8,11 +8,13 @@ using StellaOps.Scanner.Surface.FS;
using StellaOps.Scanner.WebService.Options;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
public sealed class SurfaceManifestStoreOptionsConfiguratorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Configure_UsesSurfaceEnvironmentAndCacheRoot()
{
var cacheRoot = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));

View File

@@ -9,6 +9,8 @@ using System.Net.Http.Json;
using System.Text.Json;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -18,7 +20,8 @@ public sealed class TriageStatusEndpointsTests
{
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetFindingStatus_NotFound_ReturnsNotFound()
{
using var factory = new ScannerApplicationFactory();
@@ -28,7 +31,8 @@ public sealed class TriageStatusEndpointsTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostUpdateStatus_ValidRequest_ReturnsUpdatedStatus()
{
using var factory = new ScannerApplicationFactory();
@@ -46,7 +50,8 @@ public sealed class TriageStatusEndpointsTests
Assert.True(response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostVexStatement_ValidRequest_ReturnsResponse()
{
using var factory = new ScannerApplicationFactory();
@@ -64,7 +69,8 @@ public sealed class TriageStatusEndpointsTests
Assert.True(response.StatusCode == HttpStatusCode.OK || response.StatusCode == HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostQuery_EmptyFilters_ReturnsResults()
{
using var factory = new ScannerApplicationFactory();
@@ -84,7 +90,8 @@ public sealed class TriageStatusEndpointsTests
Assert.NotNull(result.Summary);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostQuery_WithLaneFilter_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -103,7 +110,8 @@ public sealed class TriageStatusEndpointsTests
Assert.NotNull(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostQuery_WithVerdictFilter_FiltersCorrectly()
{
using var factory = new ScannerApplicationFactory();
@@ -122,7 +130,8 @@ public sealed class TriageStatusEndpointsTests
Assert.NotNull(result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSummary_ValidDigest_ReturnsSummary()
{
using var factory = new ScannerApplicationFactory();
@@ -137,7 +146,8 @@ public sealed class TriageStatusEndpointsTests
Assert.NotNull(result.ByVerdict);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSummary_IncludesAllLanes()
{
using var factory = new ScannerApplicationFactory();
@@ -154,7 +164,8 @@ public sealed class TriageStatusEndpointsTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetSummary_IncludesAllVerdicts()
{
using var factory = new ScannerApplicationFactory();
@@ -171,7 +182,8 @@ public sealed class TriageStatusEndpointsTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PostQuery_ResponseIncludesSummary()
{
using var factory = new ScannerApplicationFactory();

View File

@@ -10,6 +10,7 @@ using StellaOps.Scanner.WebService.Contracts;
using System.Text.Json;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -21,7 +22,8 @@ public sealed class UnifiedEvidenceServiceTests
{
#region UEE-9200-030: DTO Serialization Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UnifiedEvidenceResponseDto_Serializes_WithRequiredProperties()
{
// Arrange
@@ -44,7 +46,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("pkg:npm/lodash@4.17.21");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SbomEvidenceDto_Serializes_WithAllProperties()
{
// Arrange
@@ -79,7 +82,8 @@ public sealed class UnifiedEvidenceServiceTests
deserialized.Licenses().Should().Contain("MIT");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReachabilityEvidenceDto_Serializes_WithEntryPoints()
{
// Arrange
@@ -117,7 +121,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("POST /api/users");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexClaimDto_Serializes_WithTrustScore()
{
// Arrange
@@ -144,7 +149,8 @@ public sealed class UnifiedEvidenceServiceTests
deserialized.MeetsPolicyThreshold.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void AttestationSummaryDto_Serializes_WithTransparencyLog()
{
// Arrange
@@ -169,7 +175,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("rekor.sigstore.dev");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DeltaEvidenceDto_Serializes_WithSummary()
{
// Arrange
@@ -200,7 +207,8 @@ public sealed class UnifiedEvidenceServiceTests
deserialized.Summary.IsNew.Should().BeTrue();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void PolicyEvidenceDto_Serializes_WithRulesFired()
{
// Arrange
@@ -239,7 +247,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("Counterfactuals");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestHashesDto_Serializes_RequiredHashes()
{
// Arrange
@@ -269,7 +278,8 @@ public sealed class UnifiedEvidenceServiceTests
#region UEE-9200-031: Evidence Aggregation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UnifiedEvidenceResponseDto_CanHaveAllTabsPopulated()
{
// Arrange & Act
@@ -284,7 +294,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.Policy.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UnifiedEvidenceResponseDto_HandlesNullTabs_Gracefully()
{
// Arrange
@@ -316,7 +327,8 @@ public sealed class UnifiedEvidenceServiceTests
deserialized.VexClaims.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexClaims_CanContainMultipleSources()
{
// Arrange
@@ -366,7 +378,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.VexClaims!.Count(v => v.MeetsPolicyThreshold).Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Attestations_CanContainMultiplePredicateTypes()
{
// Arrange
@@ -401,7 +414,8 @@ public sealed class UnifiedEvidenceServiceTests
attestations.Count(a => a.VerificationStatus == "verified").Should().Be(2);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ReplayCommand_IsIncludedInEvidence()
{
// Arrange
@@ -429,7 +443,8 @@ public sealed class UnifiedEvidenceServiceTests
#region UEE-9200-032: Verification Status Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationStatusDto_Verified_WhenAllChecksPass()
{
// Arrange
@@ -451,7 +466,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.Issues.Should().BeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationStatusDto_Partial_WhenSomeChecksPass()
{
// Arrange
@@ -472,7 +488,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.Issues![0].Should().Contain("Attestation");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationStatusDto_Failed_WhenCriticalChecksFail()
{
// Arrange
@@ -497,7 +514,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.Issues.Should().HaveCount(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VerificationStatusDto_Unknown_WhenNoVerificationRun()
{
// Arrange
@@ -516,7 +534,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.VerifiedAt.Should().BeNull();
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(true, true, true, "verified")]
[InlineData(true, false, true, "partial")]
[InlineData(false, true, true, "partial")]
@@ -536,7 +555,8 @@ public sealed class UnifiedEvidenceServiceTests
#region UEE-9200-035: JSON Snapshot Structure Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UnifiedEvidenceResponseDto_HasExpectedJsonStructure()
{
// Arrange
@@ -560,7 +580,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("\"GeneratedAt\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void SbomComponentDto_HasExpectedJsonStructure()
{
// Arrange
@@ -586,7 +607,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("\"Cpes\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CallChainSummaryDto_HasExpectedJsonStructure()
{
// Arrange
@@ -608,7 +630,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("\"CallGraphUri\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void VexClaimDto_HasExpectedJsonStructure()
{
// Arrange
@@ -635,7 +658,8 @@ public sealed class UnifiedEvidenceServiceTests
json.Should().Contain("\"ImpactStatement\"");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ManifestHashesDto_AllHashesAreSha256Prefixed()
{
// Arrange
@@ -654,7 +678,8 @@ public sealed class UnifiedEvidenceServiceTests
dto.PolicyHash.Should().StartWith("sha256:");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UnifiedEvidenceResponseDto_RoundTrips_WithJsonSerialization()
{
// Arrange
@@ -678,7 +703,8 @@ public sealed class UnifiedEvidenceServiceTests
#region UEE-9200-033/034: Integration Test Stubs (Unit Test Versions)
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CacheKey_IsContentAddressed()
{
// Arrange
@@ -708,7 +734,8 @@ public sealed class UnifiedEvidenceServiceTests
dto1.CacheKey.Should().Be(dto2.CacheKey);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void EvidenceBundleUrl_FollowsExpectedPattern()
{
// Arrange

View File

@@ -9,6 +9,7 @@ using FluentAssertions;
using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Scanner.WebService.Tests;
/// <summary>
@@ -27,7 +28,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
_client = fixture.Factory.CreateClient();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_ReturnsOk_WhenValidRequest()
{
// Arrange
@@ -40,7 +42,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_SupportsPagination()
{
// Arrange
@@ -53,7 +56,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_SupportsBandFilter()
{
// Arrange - filter by HOT band
@@ -66,7 +70,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_SupportsSortByScore()
{
// Arrange
@@ -79,7 +84,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_SupportsSortByLastSeen()
{
// Arrange
@@ -92,7 +98,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknownById_ReturnsNotFound_WhenUnknownDoesNotExist()
{
// Arrange
@@ -105,7 +112,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknownEvidence_ReturnsNotFound_WhenUnknownDoesNotExist()
{
// Arrange
@@ -118,7 +126,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknownHistory_ReturnsNotFound_WhenUnknownDoesNotExist()
{
// Arrange
@@ -131,7 +140,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknownsStats_ReturnsOk()
{
// Arrange
@@ -144,7 +154,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknownsBandDistribution_ReturnsOk()
{
// Arrange
@@ -157,7 +168,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_BadRequest_WhenInvalidBand()
{
// Arrange
@@ -170,7 +182,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
response.StatusCode.Should().BeOneOf(HttpStatusCode.BadRequest, HttpStatusCode.OK, HttpStatusCode.NotFound);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetUnknowns_BadRequest_WhenLimitTooLarge()
{
// Arrange
@@ -190,7 +203,8 @@ public sealed class UnknownsEndpointsTests : IClassFixture<ScannerApplicationFix
/// </summary>
public sealed class UnknownsScoringTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0.9, 0.8, 0.7, 0.6, 0.5, 0.7)] // High score expected
[InlineData(0.1, 0.2, 0.3, 0.2, 0.1, 0.18)] // Low score expected
public void ComputeScore_ShouldWeightFactors(
@@ -214,7 +228,8 @@ public sealed class UnknownsScoringTests
score.Should().BeApproximately(expectedScore, 0.1);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0.75, "HOT")]
[InlineData(0.50, "WARM")]
[InlineData(0.25, "COLD")]
@@ -227,7 +242,8 @@ public sealed class UnknownsScoringTests
band.Should().Be(expectedBand);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void DecayScore_ShouldReduceOverTime()
{
// Arrange