Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 =>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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")));
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user