Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<ProjectReference Include="..\StellaOps.Excititor.Storage.Postgres\StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-preview.7.25380.108" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="20.0.28" />
|
||||
|
||||
@@ -269,9 +269,9 @@ public sealed class TimeBoxedConfidenceManager : ITimeBoxedConfidenceManager
|
||||
Id = $"tbc-{Guid.NewGuid():N}",
|
||||
CveId = statement.VulnerabilityId,
|
||||
ProductId = statement.ProductId,
|
||||
ComponentPath = statement.ComponentPath,
|
||||
Symbol = statement.Symbol,
|
||||
Confidence = statement.RuntimeScore,
|
||||
ComponentPath = statement.ProductId, // Use ProductId as path since ComponentPath doesn't exist
|
||||
Symbol = statement.Evidence.Symbol,
|
||||
Confidence = 1.0, // Default confidence - actual score calculated from evidence
|
||||
State = ConfidenceState.Provisional,
|
||||
CreatedAt = now,
|
||||
LastRefreshedAt = now,
|
||||
@@ -283,8 +283,8 @@ public sealed class TimeBoxedConfidenceManager : ITimeBoxedConfidenceManager
|
||||
{
|
||||
Timestamp = now,
|
||||
ObservationCount = 1,
|
||||
CpuPercentage = 0.0, // Initial - will be updated on refresh
|
||||
EvidenceScore = statement.RuntimeScore
|
||||
CpuPercentage = statement.Evidence.CpuPercentage,
|
||||
EvidenceScore = 1.0 // Initial score - updated on refresh
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -344,16 +344,16 @@ public sealed class TimeBoxedConfidenceManager : ITimeBoxedConfidenceManager
|
||||
.Add(new EvidenceSnapshot
|
||||
{
|
||||
Timestamp = now,
|
||||
ObservationCount = evidence.ObservationCount,
|
||||
CpuPercentage = evidence.AverageCpuPercentage,
|
||||
EvidenceScore = evidence.Score
|
||||
ObservationCount = (int)evidence.ObservationCount,
|
||||
CpuPercentage = evidence.CpuPercentage,
|
||||
EvidenceScore = CalculateEvidenceScore(evidence)
|
||||
})
|
||||
.TakeLast(_options.MaxEvidenceHistory)
|
||||
.ToImmutableArray();
|
||||
|
||||
var refreshed = existing with
|
||||
{
|
||||
Confidence = Math.Max(existing.Confidence, evidence.Score),
|
||||
Confidence = Math.Max(existing.Confidence, CalculateEvidenceScore(evidence)),
|
||||
State = newState,
|
||||
LastRefreshedAt = now,
|
||||
ExpiresAt = newExpiry,
|
||||
@@ -422,6 +422,15 @@ public sealed class TimeBoxedConfidenceManager : ITimeBoxedConfidenceManager
|
||||
return ttl;
|
||||
}
|
||||
|
||||
private static double CalculateEvidenceScore(RuntimeObservationEvidence evidence)
|
||||
{
|
||||
// Score based on observation count and CPU percentage
|
||||
// Higher observation count and CPU usage indicate stronger evidence
|
||||
var observationScore = Math.Min(evidence.ObservationCount / 100.0, 1.0);
|
||||
var cpuScore = Math.Min(evidence.CpuPercentage / 50.0, 1.0);
|
||||
return (observationScore + cpuScore) / 2.0;
|
||||
}
|
||||
|
||||
private DateTimeOffset CalculateNewExpiry(TimeBoxedConfidence existing, DateTimeOffset now)
|
||||
{
|
||||
// Extend by refresh extension, capped at max TTL from creation
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Excititor.Core.AutoVex;
|
||||
@@ -479,12 +480,14 @@ public sealed class NotReachableJustificationService : INotReachableJustificatio
|
||||
// Sign if signing service available
|
||||
if (_signingService != null)
|
||||
{
|
||||
var payload = JsonSerializer.SerializeToUtf8Bytes(statement);
|
||||
var payloadBase64 = Convert.ToBase64String(payload);
|
||||
var envelope = await _signingService.SignAsync(
|
||||
statement,
|
||||
payloadBase64,
|
||||
"application/vnd.stellaops.vex.not-reachable+json",
|
||||
cancellationToken);
|
||||
|
||||
statement = statement with { DsseEnvelope = envelope };
|
||||
statement = statement with { DsseEnvelope = (DsseEnvelope)envelope };
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
|
||||
@@ -4,11 +4,14 @@ using Moq;
|
||||
using StellaOps.Excititor.ArtifactStores.S3;
|
||||
using StellaOps.Excititor.Export;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.ArtifactStores.S3.Tests;
|
||||
|
||||
public sealed class S3ArtifactClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ObjectExistsAsync_ReturnsTrue_WhenMetadataSucceeds()
|
||||
{
|
||||
var mock = new Mock<IAmazonS3>();
|
||||
@@ -22,7 +25,8 @@ public sealed class S3ArtifactClientTests
|
||||
Assert.True(exists);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PutObjectAsync_MapsMetadata()
|
||||
{
|
||||
var mock = new Mock<IAmazonS3>();
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.ArtifactStores.S3/StellaOps.Excititor.ArtifactStores.S3.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -29,5 +29,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Attestation/StellaOps.Excititor.Attestation.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,11 +7,13 @@ using StellaOps.Excititor.Attestation.Transparency;
|
||||
using StellaOps.Excititor.Attestation.Verification;
|
||||
using StellaOps.Excititor.Core;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Attestation.Tests;
|
||||
|
||||
public sealed class VexAttestationClientTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_ReturnsEnvelopeDigestAndDiagnostics()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -36,7 +38,8 @@ public sealed class VexAttestationClientTests
|
||||
Assert.True(response.Diagnostics.ContainsKey("envelope"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SignAsync_SubmitsToTransparencyLog_WhenConfigured()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
|
||||
@@ -10,13 +10,15 @@ using StellaOps.Excititor.Attestation.Verification;
|
||||
using StellaOps.Excititor.Core;
|
||||
using ICryptoProvider = StellaOps.Cryptography.ICryptoProvider;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Attestation.Tests;
|
||||
|
||||
public sealed class VexAttestationVerifierTests : IDisposable
|
||||
{
|
||||
private readonly VexAttestationMetrics _metrics = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsValid_WhenEnvelopeMatches()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync();
|
||||
@@ -31,7 +33,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Null(verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsInvalid_WhenDigestMismatch()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync();
|
||||
@@ -53,7 +56,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Equal("envelope_digest_mismatch", verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_AllowsOfflineTransparency_WhenConfigured()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true);
|
||||
@@ -74,7 +78,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Null(verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyRequiredAndMissing()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: false);
|
||||
@@ -94,7 +99,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Equal("missing", verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsInvalid_WhenTransparencyUnavailableAndOfflineDisallowed()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: true);
|
||||
@@ -115,7 +121,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Equal("unreachable", verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_HandlesDuplicateSourceProviders()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(
|
||||
@@ -133,7 +140,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Equal("valid", verification.Diagnostics.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsValid_WhenTrustedSignerConfigured()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: false);
|
||||
@@ -161,7 +169,8 @@ public sealed class VexAttestationVerifierTests : IDisposable
|
||||
Assert.Null(verification.Diagnostics.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_ReturnsInvalid_WhenSignatureFailsAndRequired()
|
||||
{
|
||||
var (request, metadata, envelope) = await CreateSignedAttestationAsync(includeRekor: false);
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.Attestation.Models;
|
||||
using StellaOps.Excititor.Attestation.Signing;
|
||||
using StellaOps.Excititor.Core;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Attestation.Tests;
|
||||
|
||||
public sealed class VexDsseBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateEnvelopeAsync_ProducesDeterministicPayload()
|
||||
{
|
||||
var signer = new FakeSigner("signature-value", "key-1");
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class CiscoCsafNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-cisco-sa.json", "typical-cisco-sa.canonical.json")]
|
||||
[InlineData("edge-multi-product-status.json", "edge-multi-product-status.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -67,7 +68,8 @@ public sealed class CiscoCsafNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-malformed-dates.json", "error-malformed-dates.error.json")]
|
||||
public async Task Normalize_ErrorFixture_ProducesExpectedOutput(string fixtureFile, string expectedFile)
|
||||
{
|
||||
@@ -85,7 +87,8 @@ public sealed class CiscoCsafNormalizerTests
|
||||
batch.Claims.Length.Should().Be(expected!.Claims.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-cisco-sa.json")]
|
||||
[InlineData("edge-multi-product-status.json")]
|
||||
public async Task Normalize_SameInput_ProducesDeterministicOutput(string fixtureFile)
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class MsrcCsafNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-msrc.json", "typical-msrc.canonical.json")]
|
||||
[InlineData("edge-multi-cve.json", "edge-multi-cve.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -67,7 +68,8 @@ public sealed class MsrcCsafNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-msrc.json")]
|
||||
[InlineData("edge-multi-cve.json")]
|
||||
public async Task Normalize_SameInput_ProducesDeterministicOutput(string fixtureFile)
|
||||
|
||||
@@ -34,7 +34,8 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-oci-vex.json")]
|
||||
[InlineData("edge-multi-subject.json")]
|
||||
public async Task Fixture_IsValidInTotoStatement(string fixtureFile)
|
||||
@@ -53,7 +54,8 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
statement.Predicate.Should().NotBeNull();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-oci-vex.json")]
|
||||
[InlineData("edge-multi-subject.json")]
|
||||
public async Task Fixture_PredicateContainsOpenVexStatements(string fixtureFile)
|
||||
@@ -77,7 +79,8 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-oci-vex.json", "typical-oci-vex.canonical.json")]
|
||||
[InlineData("edge-multi-subject.json", "edge-multi-subject.canonical.json")]
|
||||
public async Task Expected_MatchesFixtureVulnerabilities(string fixtureFile, string expectedFile)
|
||||
@@ -108,7 +111,8 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-invalid-predicate.json")]
|
||||
public async Task ErrorFixture_HasInvalidOrMissingPredicate(string fixtureFile)
|
||||
{
|
||||
@@ -128,7 +132,8 @@ public sealed class OciOpenVexAttestNormalizerTests
|
||||
"Error fixture should not contain valid VEX statements");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-oci-vex.json")]
|
||||
[InlineData("edge-multi-subject.json")]
|
||||
public async Task Fixture_SameInput_ProducesDeterministicParsing(string fixtureFile)
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class OracleCsafNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-cpu.json", "typical-cpu.canonical.json")]
|
||||
[InlineData("edge-multi-version.json", "edge-multi-version.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -67,7 +68,8 @@ public sealed class OracleCsafNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-missing-vulnerabilities.json", "error-missing-vulnerabilities.error.json")]
|
||||
public async Task Normalize_ErrorFixture_ProducesExpectedOutput(string fixtureFile, string expectedFile)
|
||||
{
|
||||
@@ -85,7 +87,8 @@ public sealed class OracleCsafNormalizerTests
|
||||
batch.Claims.Length.Should().Be(expected!.Claims.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-cpu.json")]
|
||||
[InlineData("edge-multi-version.json")]
|
||||
public async Task Normalize_SameInput_ProducesDeterministicOutput(string fixtureFile)
|
||||
|
||||
@@ -38,7 +38,8 @@ public sealed class RedHatCsafNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-rhsa.json", "typical-rhsa.canonical.json")]
|
||||
[InlineData("edge-multi-product.json", "edge-multi-product.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -78,7 +79,8 @@ public sealed class RedHatCsafNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-missing-tracking.json", "error-missing-tracking.error.json")]
|
||||
public async Task Normalize_ErrorFixture_ProducesExpectedOutput(string fixtureFile, string expectedFile)
|
||||
{
|
||||
@@ -96,7 +98,8 @@ public sealed class RedHatCsafNormalizerTests
|
||||
batch.Claims.Length.Should().Be(expected!.Claims.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-rhsa.json")]
|
||||
[InlineData("edge-multi-product.json")]
|
||||
[InlineData("error-missing-tracking.json")]
|
||||
@@ -120,7 +123,8 @@ public sealed class RedHatCsafNormalizerTests
|
||||
$"parsing '{fixtureFile}' multiple times should produce identical output");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CanHandle_CsafDocument_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
@@ -138,7 +142,8 @@ public sealed class RedHatCsafNormalizerTests
|
||||
canHandle.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CanHandle_NonCsafDocument_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class RancherVexHubNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-rancher.json", "typical-rancher.canonical.json")]
|
||||
[InlineData("edge-status-transitions.json", "edge-status-transitions.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -67,7 +68,8 @@ public sealed class RancherVexHubNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-missing-statements.json", "error-missing-statements.error.json")]
|
||||
public async Task Normalize_ErrorFixture_ProducesExpectedOutput(string fixtureFile, string expectedFile)
|
||||
{
|
||||
@@ -85,7 +87,8 @@ public sealed class RancherVexHubNormalizerTests
|
||||
batch.Claims.Length.Should().Be(expected!.Claims.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-rancher.json")]
|
||||
[InlineData("edge-status-transitions.json")]
|
||||
public async Task Normalize_SameInput_ProducesDeterministicOutput(string fixtureFile)
|
||||
@@ -107,7 +110,8 @@ public sealed class RancherVexHubNormalizerTests
|
||||
results.Distinct().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CanHandle_OpenVexDocument_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed class UbuntuCsafNormalizerTests
|
||||
_expectedDir = Path.Combine(AppContext.BaseDirectory, "Expected");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-usn.json", "typical-usn.canonical.json")]
|
||||
[InlineData("edge-multi-release.json", "edge-multi-release.canonical.json")]
|
||||
public async Task Normalize_Fixture_ProducesExpectedClaims(string fixtureFile, string expectedFile)
|
||||
@@ -67,7 +68,8 @@ public sealed class UbuntuCsafNormalizerTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("error-empty-products.json", "error-empty-products.error.json")]
|
||||
public async Task Normalize_ErrorFixture_ProducesExpectedOutput(string fixtureFile, string expectedFile)
|
||||
{
|
||||
@@ -85,7 +87,8 @@ public sealed class UbuntuCsafNormalizerTests
|
||||
batch.Claims.Length.Should().Be(expected!.Claims.Count);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("typical-usn.json")]
|
||||
[InlineData("edge-multi-release.json")]
|
||||
public async Task Normalize_SameInput_ProducesDeterministicOutput(string fixtureFile)
|
||||
|
||||
@@ -22,5 +22,6 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Policy/StellaOps.Excititor.Policy.csproj" />
|
||||
<ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
|
||||
<ProjectReference Include="../../../Concelier/__Libraries/StellaOps.Concelier.RawModels/StellaOps.Concelier.RawModels.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,11 +5,13 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public sealed class VexAttestationPayloadTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Payload_NormalizesAndOrdersMetadata()
|
||||
{
|
||||
var metadata = ImmutableDictionary<string, string>.Empty
|
||||
@@ -35,7 +37,8 @@ public sealed class VexAttestationPayloadTests
|
||||
payload.Metadata.Should().ContainKey("c").WhoseValue.Should().Be("value-c");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Payload_TrimsWhitespaceFromValues()
|
||||
{
|
||||
var metadata = ImmutableDictionary<string, string>.Empty
|
||||
@@ -60,7 +63,8 @@ public sealed class VexAttestationPayloadTests
|
||||
payload.Metadata["key"].Should().Be("value");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Payload_OmitsNullOrWhitespaceMetadataEntries()
|
||||
{
|
||||
var metadata = ImmutableDictionary<string, string>.Empty
|
||||
@@ -84,7 +88,8 @@ public sealed class VexAttestationPayloadTests
|
||||
payload.JustificationSummary.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Payload_NormalizesIssuedAtToUtc()
|
||||
{
|
||||
var localTime = new DateTimeOffset(2024, 6, 15, 10, 30, 0, TimeSpan.FromHours(5));
|
||||
@@ -104,7 +109,8 @@ public sealed class VexAttestationPayloadTests
|
||||
payload.IssuedAt.UtcDateTime.Should().Be(localTime.UtcDateTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Payload_ThrowsOnMissingRequiredFields()
|
||||
{
|
||||
var action = () => new VexAttestationPayload(
|
||||
@@ -122,7 +128,8 @@ public sealed class VexAttestationPayloadTests
|
||||
.WithMessage("*attestationId*");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AttestationLink_ValidatesRequiredFields()
|
||||
{
|
||||
var link = new VexAttestationLink(
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Collections.Immutable;
|
||||
using StellaOps.Excititor.Core;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public sealed class VexCanonicalJsonSerializerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializeClaim_ProducesDeterministicOrder()
|
||||
{
|
||||
var product = new VexProduct(
|
||||
@@ -56,7 +58,8 @@ public sealed class VexCanonicalJsonSerializerTests
|
||||
json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializeConsensus_IncludesSignalsInOrder()
|
||||
{
|
||||
var product = new VexProduct("pkg:demo/app", "Demo App");
|
||||
@@ -87,7 +90,8 @@ public sealed class VexCanonicalJsonSerializerTests
|
||||
json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void QuerySignature_FromFilters_SortsAndNormalizesKeys()
|
||||
{
|
||||
var signature = VexQuerySignature.FromFilters(new[]
|
||||
@@ -100,7 +104,8 @@ public sealed class VexCanonicalJsonSerializerTests
|
||||
Assert.Equal("provider=canonical&provider=redhat&vulnId=CVE-2025-12345", signature.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void SerializeExportManifest_OrdersArraysAndNestedObjects()
|
||||
{
|
||||
var manifest = new VexExportManifest(
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.IO;
|
||||
using System.Text;
|
||||
using StellaOps.Excititor.Policy;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public sealed class VexPolicyBinderTests
|
||||
@@ -36,7 +38,8 @@ public sealed class VexPolicyBinderTests
|
||||
provider-b: 0.3
|
||||
""";
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Bind_Json_ReturnsNormalizedOptions()
|
||||
{
|
||||
var result = VexPolicyBinder.Bind(JsonPolicy, VexPolicyDocumentFormat.Json);
|
||||
@@ -55,7 +58,8 @@ public sealed class VexPolicyBinderTests
|
||||
Assert.Empty(result.Issues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Bind_Yaml_ReturnsOverridesAndWarningsSorted()
|
||||
{
|
||||
var result = VexPolicyBinder.Bind(YamlPolicy, VexPolicyDocumentFormat.Yaml);
|
||||
@@ -69,7 +73,8 @@ public sealed class VexPolicyBinderTests
|
||||
Assert.Empty(result.Issues);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Bind_InvalidJson_ReturnsError()
|
||||
{
|
||||
const string invalidJson = "{ \"weights\": { \"vendor\": \"not-a-number\" }";
|
||||
@@ -82,7 +87,8 @@ public sealed class VexPolicyBinderTests
|
||||
Assert.StartsWith("policy.parse.json", issue.Code, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Bind_Stream_SupportsEncoding()
|
||||
{
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonPolicy));
|
||||
@@ -92,7 +98,8 @@ public sealed class VexPolicyBinderTests
|
||||
Assert.NotNull(result.Options);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Bind_InvalidWeightsAndScoring_EmitsWarningsAndClamps()
|
||||
{
|
||||
const string policy = """
|
||||
|
||||
@@ -9,11 +9,14 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Policy;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public class VexPolicyDiagnosticsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetDiagnostics_ReportsCountsRecommendationsAndOverrides()
|
||||
{
|
||||
var overrides = new[]
|
||||
@@ -55,7 +58,8 @@ public class VexPolicyDiagnosticsTests
|
||||
Assert.Contains(report.Recommendations, message => message.Contains("docs/modules/excititor/architecture.md", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetDiagnostics_WhenNoIssues_StillReturnsDefaultRecommendation()
|
||||
{
|
||||
var fakeProvider = new FakePolicyProvider(VexPolicySnapshot.Default);
|
||||
@@ -70,7 +74,8 @@ public class VexPolicyDiagnosticsTests
|
||||
Assert.Single(report.Recommendations);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void PolicyProvider_ComputesRevisionAndDigest_AndEmitsTelemetry()
|
||||
{
|
||||
using var listener = new MeterListener();
|
||||
|
||||
@@ -2,11 +2,13 @@ using System.Collections.Generic;
|
||||
using StellaOps.Excititor.Core;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public sealed class VexQuerySignatureTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromFilters_SortsAlphabetically()
|
||||
{
|
||||
var filters = new[]
|
||||
@@ -21,7 +23,8 @@ public sealed class VexQuerySignatureTests
|
||||
Assert.Equal("provider=cisco&provider=redhat&vulnId=CVE-2025-0001", signature.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void FromQuery_NormalizesFiltersAndSort()
|
||||
{
|
||||
var query = VexQuery.Create(
|
||||
@@ -46,7 +49,8 @@ public sealed class VexQuerySignatureTests
|
||||
signature.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ComputeHash_ReturnsStableSha256()
|
||||
{
|
||||
var signature = new VexQuerySignature("provider=redhat&vulnId=CVE-2025-0003");
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.Tests;
|
||||
|
||||
public sealed class VexSignalSnapshotTests
|
||||
{
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(-0.01)]
|
||||
[InlineData(1.01)]
|
||||
[InlineData(double.NaN)]
|
||||
@@ -15,7 +17,8 @@ public sealed class VexSignalSnapshotTests
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => new VexSignalSnapshot(epss: value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(null)]
|
||||
@@ -24,7 +27,8 @@ public sealed class VexSignalSnapshotTests
|
||||
Assert.Throws<ArgumentException>(() => new VexSeveritySignal(scheme!));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Theory]
|
||||
[InlineData(-0.1)]
|
||||
[InlineData(double.NaN)]
|
||||
[InlineData(double.NegativeInfinity)]
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" PrivateAssets="all" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Storage.Postgres/StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
|
||||
@@ -3,11 +3,13 @@ using System.Collections.Immutable;
|
||||
using StellaOps.Excititor.Core.Observations;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.UnitTests;
|
||||
|
||||
public class TimelineEventTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_NormalizesFields_AndPreservesValues()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -42,7 +44,8 @@ public class TimelineEventTests
|
||||
Assert.Equal("value1", evt.Attributes["key1"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_ThrowsOnNullOrWhiteSpaceRequiredFields()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -78,7 +81,8 @@ public class TimelineEventTests
|
||||
createdAt: now));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_HandlesNullOptionalFields()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -102,7 +106,8 @@ public class TimelineEventTests
|
||||
Assert.Empty(evt.Attributes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Constructor_FiltersNullAttributeKeysAndValues()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -127,7 +132,8 @@ public class TimelineEventTests
|
||||
Assert.True(evt.Attributes.ContainsKey("valid-key"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void EventTypes_Constants_AreCorrect()
|
||||
{
|
||||
Assert.Equal("vex.observation.ingested", VexTimelineEventTypes.ObservationIngested);
|
||||
@@ -142,7 +148,8 @@ public class TimelineEventTests
|
||||
Assert.Equal("vex.attestation.verified", VexTimelineEventTypes.AttestationVerified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void AttributeKeys_Constants_AreCorrect()
|
||||
{
|
||||
Assert.Equal("observation_id", VexTimelineEventAttributes.ObservationId);
|
||||
|
||||
@@ -11,11 +11,13 @@ using StellaOps.Excititor.Core.Evidence;
|
||||
using StellaOps.Excititor.Core.Observations;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.UnitTests;
|
||||
|
||||
public class VexEvidenceAttestorTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AttestManifestAsync_CreatesValidAttestation()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -42,7 +44,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.NotNull(result.SignedManifest.Signature);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AttestManifestAsync_EnvelopeContainsCorrectPayload()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -72,7 +75,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.Equal(VexEvidenceInTotoStatement.EvidenceLockerPredicateType, statement["predicateType"]?.GetValue<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAttestationAsync_ReturnsValidForCorrectAttestation()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -97,7 +101,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.True(verification.Diagnostics.ContainsKey("envelope_hash"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAttestationAsync_ReturnsInvalidForWrongManifest()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -127,7 +132,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.Contains("Manifest ID mismatch", verification.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAttestationAsync_ReturnsInvalidForInvalidJson()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -150,7 +156,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.Contains("JSON parse error", verification.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAttestationAsync_ReturnsInvalidForEmptyEnvelope()
|
||||
{
|
||||
var signer = new FakeSigner();
|
||||
@@ -173,7 +180,8 @@ public class VexEvidenceAttestorTests
|
||||
Assert.Equal("DSSE envelope is required.", verification.FailureReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceAttestationPredicate_FromManifest_CapturesAllFields()
|
||||
{
|
||||
var item = new VexEvidenceSnapshotItem(
|
||||
|
||||
@@ -9,11 +9,13 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.UnitTests;
|
||||
|
||||
public sealed class VexEvidenceChunkServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_FiltersAndLimitsResults()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 11, 16, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.Core.Evidence;
|
||||
using StellaOps.Excititor.Core.Observations;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.UnitTests;
|
||||
|
||||
public class VexEvidenceLockerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceSnapshotItem_NormalizesFields()
|
||||
{
|
||||
var item = new VexEvidenceSnapshotItem(
|
||||
@@ -26,7 +28,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("ingest", item.Provenance.Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceProvenance_CreatesCorrectProvenance()
|
||||
{
|
||||
var provenance = new VexEvidenceProvenance("mirror", 5, "sha256:manifest123");
|
||||
@@ -36,7 +39,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("sha256:manifest123", provenance.ExportCenterManifest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexLockerManifest_SortsItemsDeterministically()
|
||||
{
|
||||
var item1 = new VexEvidenceSnapshotItem("obs-002", "provider-b", "sha256:bbb", "linkset-1");
|
||||
@@ -58,7 +62,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("obs-002", manifest.Items[2].ObservationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexLockerManifest_ComputesMerkleRoot()
|
||||
{
|
||||
var item1 = new VexEvidenceSnapshotItem("obs-001", "provider-a", "sha256:0000000000000000000000000000000000000000000000000000000000000001", "linkset-1");
|
||||
@@ -74,7 +79,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal(71, manifest.MerkleRoot.Length); // "sha256:" + 64 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexLockerManifest_CreateManifestId_GeneratesCorrectFormat()
|
||||
{
|
||||
var id = VexLockerManifest.CreateManifestId("TestTenant", DateTimeOffset.Parse("2025-11-27T15:30:00Z"), 42);
|
||||
@@ -82,7 +88,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("locker:excititor:testtenant:2025-11-27:0042", id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexLockerManifest_WithSignature_PreservesData()
|
||||
{
|
||||
var item = new VexEvidenceSnapshotItem("obs-001", "provider-a", "sha256:abc123", "linkset-1");
|
||||
@@ -100,7 +107,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal(manifest.Items.Length, signed.Items.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceLockerService_CreateSnapshotItem_FromObservation()
|
||||
{
|
||||
var observation = BuildTestObservation("obs-001", "provider-a", "sha256:content123");
|
||||
@@ -114,7 +122,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("linkset-001", item.LinksetId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceLockerService_BuildManifest_CreatesValidManifest()
|
||||
{
|
||||
var obs1 = BuildTestObservation("obs-001", "provider-a", "sha256:aaa");
|
||||
@@ -136,7 +145,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.Equal("true", manifest.Metadata["sealed"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexEvidenceLockerService_VerifyManifest_ReturnsTrueForValidManifest()
|
||||
{
|
||||
var item = new VexEvidenceSnapshotItem("obs-001", "provider-a", "sha256:0000000000000000000000000000000000000000000000000000000000000001", "linkset-1");
|
||||
@@ -150,7 +160,8 @@ public class VexEvidenceLockerTests
|
||||
Assert.True(service.VerifyManifest(manifest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void VexLockerManifest_EmptyItems_ProducesEmptyMerkleRoot()
|
||||
{
|
||||
var manifest = new VexLockerManifest(
|
||||
|
||||
@@ -5,11 +5,13 @@ using System.Text.Json.Nodes;
|
||||
using StellaOps.Excititor.Core.Observations;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Core.UnitTests;
|
||||
|
||||
public class VexLinksetExtractionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Extract_GroupsByVulnerabilityAndProduct_WithStableOrdering()
|
||||
{
|
||||
var obs1 = BuildObservation(
|
||||
@@ -57,7 +59,8 @@ public class VexLinksetExtractionServiceTests
|
||||
Assert.Equal(DateTimeOffset.Parse("2025-11-21T09:00:00Z").ToUniversalTime(), second.CreatedAtUtc);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Extract_FiltersNullsAndReturnsEmptyWhenNoObservations()
|
||||
{
|
||||
var service = new VexLinksetExtractionService();
|
||||
|
||||
@@ -10,11 +10,13 @@ using StellaOps.Excititor.Export;
|
||||
using StellaOps.Excititor.Policy;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class ExportEngineTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_GeneratesAndCachesManifest()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
@@ -51,7 +53,8 @@ public sealed class ExportEngineTests
|
||||
Assert.Equal(manifest.ExportId, cached.ExportId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_ForceRefreshInvalidatesCacheEntry()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
@@ -74,7 +77,8 @@ public sealed class ExportEngineTests
|
||||
Assert.True(removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_WritesArtifactsToAllStores()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
@@ -101,7 +105,8 @@ public sealed class ExportEngineTests
|
||||
Assert.Equal(1, recorder2.SaveCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_AttachesAttestationMetadata()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
@@ -158,7 +163,8 @@ public sealed class ExportEngineTests
|
||||
Assert.Equal(manifest.QuietProvenance, store.LastSavedManifest!.QuietProvenance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportAsync_IncludesQuietProvenanceMetadata()
|
||||
{
|
||||
var store = new InMemoryExportStore();
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class FileSystemArtifactStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_WritesArtifactToDisk()
|
||||
{
|
||||
var fs = new MockFileSystem();
|
||||
|
||||
@@ -15,11 +15,14 @@ using System.Collections.Immutable;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class MirrorBundlePublisherTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PublishAsync_WritesMirrorArtifacts()
|
||||
{
|
||||
var generatedAt = DateTimeOffset.Parse("2025-10-21T12:00:00Z");
|
||||
@@ -175,7 +178,8 @@ public sealed class MirrorBundlePublisherTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PublishAsync_NoMatchingDomain_DoesNotWriteArtifacts()
|
||||
{
|
||||
var generatedAt = DateTimeOffset.Parse("2025-10-21T12:00:00Z");
|
||||
|
||||
@@ -7,11 +7,14 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class OfflineBundleArtifactStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_WritesArtifactAndManifest()
|
||||
{
|
||||
var fs = new MockFileSystem();
|
||||
@@ -41,7 +44,8 @@ public sealed class OfflineBundleArtifactStoreTests
|
||||
Assert.Equal(digest, first.GetProperty("digest").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_ThrowsOnDigestMismatch()
|
||||
{
|
||||
var fs = new MockFileSystem();
|
||||
|
||||
@@ -5,11 +5,14 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class S3ArtifactStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_UploadsContentWithMetadata()
|
||||
{
|
||||
var client = new FakeS3Client();
|
||||
@@ -33,7 +36,8 @@ public sealed class S3ArtifactStoreTests
|
||||
Assert.Equal("sha256:deadbeef", entry.Metadata["vex-digest"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenReadAsync_ReturnsStoredContent()
|
||||
{
|
||||
var client = new FakeS3Client();
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Export/StellaOps.Excititor.Export.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -2,11 +2,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Export.Tests;
|
||||
|
||||
public sealed class VexExportCacheServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InvalidateAsync_RemovesEntry()
|
||||
{
|
||||
var cacheIndex = new RecordingIndex();
|
||||
@@ -21,7 +23,8 @@ public sealed class VexExportCacheServiceTests
|
||||
Assert.Equal(1, cacheIndex.RemoveCalls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PruneExpiredAsync_ReturnsCount()
|
||||
{
|
||||
var cacheIndex = new RecordingIndex();
|
||||
@@ -33,7 +36,8 @@ public sealed class VexExportCacheServiceTests
|
||||
Assert.Equal(3, removed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task PruneDanglingAsync_ReturnsCount()
|
||||
{
|
||||
var cacheIndex = new RecordingIndex();
|
||||
|
||||
@@ -4,11 +4,14 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.CSAF;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.CSAF.Tests;
|
||||
|
||||
public sealed class CsafExporterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SerializeAsync_WritesDeterministicCsafDocument()
|
||||
{
|
||||
var claims = ImmutableArray.Create(
|
||||
|
||||
@@ -7,11 +7,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.CSAF;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.CSAF.Tests;
|
||||
|
||||
public sealed class CsafNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_ProducesClaimsPerProductStatus()
|
||||
{
|
||||
var json = """
|
||||
@@ -92,7 +94,8 @@ public sealed class CsafNormalizerTests
|
||||
notAffectedClaim.Status.Should().Be(VexClaimStatus.NotAffected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_PreservesRedHatSpecificMetadata()
|
||||
{
|
||||
var path = Path.Combine(AppContext.BaseDirectory, "Fixtures", "rhsa-sample.json");
|
||||
@@ -129,7 +132,8 @@ public sealed class CsafNormalizerTests
|
||||
claim.AdditionalMetadata["csaf.publisher.name"].Should().Be("Red Hat Product Security");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_MissingJustification_AddsPolicyDiagnostic()
|
||||
{
|
||||
var json = """
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Formats.CSAF/StellaOps.Excititor.Formats.CSAF.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Fixtures\**\*" CopyToOutputDirectory="Always" />
|
||||
|
||||
@@ -3,11 +3,13 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.CycloneDX;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.CycloneDX.Tests;
|
||||
|
||||
public sealed class CycloneDxComponentReconcilerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Reconcile_AssignsBomRefsAndDiagnostics()
|
||||
{
|
||||
var claims = ImmutableArray.Create(
|
||||
|
||||
@@ -5,11 +5,14 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.CycloneDX;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.CycloneDX.Tests;
|
||||
|
||||
public sealed class CycloneDxExporterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SerializeAsync_WritesCycloneDxVexDocument()
|
||||
{
|
||||
var claims = ImmutableArray.Create(
|
||||
|
||||
@@ -6,11 +6,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.CycloneDX;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.CycloneDX.Tests;
|
||||
|
||||
public sealed class CycloneDxNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_MapsAnalysisStateAndJustification()
|
||||
{
|
||||
var json = """
|
||||
@@ -91,7 +93,8 @@ public sealed class CycloneDxNormalizerTests
|
||||
investigating.AdditionalMetadata.Should().ContainKey("cyclonedx.specVersion");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_NormalizesSpecVersion()
|
||||
{
|
||||
var json = """
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Formats.CycloneDX/StellaOps.Excititor.Formats.CycloneDX.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -4,11 +4,14 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.OpenVEX;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.OpenVEX.Tests;
|
||||
|
||||
public sealed class OpenVexExporterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SerializeAsync_ProducesCanonicalOpenVexDocument()
|
||||
{
|
||||
var claims = ImmutableArray.Create(
|
||||
|
||||
@@ -6,11 +6,13 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Formats.OpenVEX;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.OpenVEX.Tests;
|
||||
|
||||
public sealed class OpenVexNormalizerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task NormalizeAsync_ProducesClaimsForStatements()
|
||||
{
|
||||
var json = """
|
||||
|
||||
@@ -6,6 +6,7 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Core.Lattice;
|
||||
using StellaOps.Excititor.Formats.OpenVEX;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Formats.OpenVEX.Tests;
|
||||
|
||||
public sealed class OpenVexStatementMergerTests
|
||||
@@ -23,7 +24,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
NullLogger<OpenVexStatementMerger>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Merge_DetectsConflictsAndSelectsCanonicalStatus()
|
||||
{
|
||||
var merger = CreateMerger();
|
||||
@@ -55,7 +57,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
result.Diagnostics.Should().ContainKey("openvex.status_conflict");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MergeClaims_NoStatements_ReturnsEmpty()
|
||||
{
|
||||
var merger = CreateMerger();
|
||||
@@ -66,7 +69,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
result.HadConflicts.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MergeClaims_SingleStatement_ReturnsSingle()
|
||||
{
|
||||
var merger = CreateMerger();
|
||||
@@ -79,7 +83,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
result.HadConflicts.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MergeClaims_ConflictingStatements_UsesLattice()
|
||||
{
|
||||
var merger = CreateMerger();
|
||||
@@ -94,7 +99,8 @@ public sealed class OpenVexStatementMergerTests
|
||||
result.ResultStatement.Status.Should().Be(VexClaimStatus.Affected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void MergeClaims_MultipleStatements_CollectsAllTraces()
|
||||
{
|
||||
var merger = CreateMerger();
|
||||
|
||||
@@ -14,5 +14,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Core/StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Formats.OpenVEX/StellaOps.Excititor.Formats.OpenVEX.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -9,5 +9,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Policy/StellaOps.Excititor.Policy.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -6,11 +6,13 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Policy;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Policy.Tests;
|
||||
|
||||
public sealed class VexPolicyProviderTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSnapshot_UsesDefaultsWhenOptionsMissing()
|
||||
{
|
||||
var provider = new VexPolicyProvider(
|
||||
@@ -41,7 +43,8 @@ public sealed class VexPolicyProviderTests
|
||||
Assert.Equal("missing_justification", reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void GetSnapshot_AppliesOverridesAndClampsInvalidValues()
|
||||
{
|
||||
var options = new VexPolicyOptions
|
||||
|
||||
@@ -7,6 +7,8 @@ using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
@@ -60,7 +62,8 @@ public sealed class PostgresAppendOnlyLinksetStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AppendObservation_CreatesLinksetAndDedupes()
|
||||
{
|
||||
var tenant = "tenant-a";
|
||||
@@ -87,7 +90,8 @@ public sealed class PostgresAppendOnlyLinksetStoreTests : IAsyncLifetime
|
||||
mutations.Should().HaveCount(2); // created + observation
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AppendBatch_AppendsMultipleAndMaintainsOrder()
|
||||
{
|
||||
var tenant = "tenant-b";
|
||||
@@ -114,7 +118,8 @@ public sealed class PostgresAppendOnlyLinksetStoreTests : IAsyncLifetime
|
||||
result.SequenceNumber.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task AppendDisagreement_RegistersConflictAndCounts()
|
||||
{
|
||||
var tenant = "tenant-c";
|
||||
|
||||
@@ -8,6 +8,7 @@ using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
@@ -48,7 +49,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndFindById_RoundTripsAttestation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -68,7 +70,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
fetched.Metadata.Should().ContainKey("source");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindByIdAsync_ReturnsNullForUnknownId()
|
||||
{
|
||||
// Act
|
||||
@@ -78,7 +81,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindByManifestIdAsync_ReturnsMatchingAttestation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -94,7 +98,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
fetched.ManifestId.Should().Be("manifest-target");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_UpdatesExistingAttestation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -122,7 +127,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
fetched.Metadata.Should().ContainKey("version");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountAsync_ReturnsCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -137,7 +143,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
count.Should().Be(3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsPaginatedResults()
|
||||
{
|
||||
// Arrange
|
||||
@@ -157,7 +164,8 @@ public sealed class PostgresVexAttestationStoreTests : IAsyncLifetime
|
||||
result.HasMore.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_FiltersBySinceAndUntil()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -10,6 +10,7 @@ using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
@@ -50,7 +51,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InsertAndGetById_RoundTripsObservation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -70,7 +72,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
fetched.Statements[0].ProductKey.Should().Be("pkg:npm/lodash@4.17.21");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNullForUnknownId()
|
||||
{
|
||||
// Act
|
||||
@@ -80,7 +83,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InsertAsync_ReturnsFalseForDuplicateId()
|
||||
{
|
||||
// Arrange
|
||||
@@ -95,7 +99,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
second.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task UpsertAsync_UpdatesExistingObservation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -113,7 +118,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
fetched.Statements[0].ProductKey.Should().Be("pkg:npm/new@2.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindByProviderAsync_ReturnsMatchingObservations()
|
||||
{
|
||||
// Arrange
|
||||
@@ -129,7 +135,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
found.Select(o => o.ObservationId).Should().Contain("obs-p1", "obs-p2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountAsync_ReturnsCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -143,7 +150,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
count.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DeleteAsync_RemovesObservation()
|
||||
{
|
||||
// Arrange
|
||||
@@ -158,7 +166,8 @@ public sealed class PostgresVexObservationStoreTests : IAsyncLifetime
|
||||
fetched.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InsertManyAsync_InsertsMultipleObservations()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -7,6 +7,7 @@ using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
@@ -46,7 +47,8 @@ public sealed class PostgresVexProviderStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndFind_RoundTripsProvider()
|
||||
{
|
||||
// Arrange
|
||||
@@ -74,7 +76,8 @@ public sealed class PostgresVexProviderStoreTests : IAsyncLifetime
|
||||
fetched.BaseUris.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindAsync_ReturnsNullForUnknownId()
|
||||
{
|
||||
// Act
|
||||
@@ -84,7 +87,8 @@ public sealed class PostgresVexProviderStoreTests : IAsyncLifetime
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_UpdatesExistingProvider()
|
||||
{
|
||||
// Arrange
|
||||
@@ -109,7 +113,8 @@ public sealed class PostgresVexProviderStoreTests : IAsyncLifetime
|
||||
fetched.BaseUris.Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListAsync_ReturnsAllProviders()
|
||||
{
|
||||
// Arrange
|
||||
@@ -131,7 +136,8 @@ public sealed class PostgresVexProviderStoreTests : IAsyncLifetime
|
||||
providers.Select(p => p.Id).Should().ContainInOrder("aaa-provider", "zzz-provider");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAsync_PersistsTrustSettings()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -8,6 +8,7 @@ using StellaOps.Excititor.Storage.Postgres.Repositories;
|
||||
using StellaOps.Infrastructure.Postgres.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Storage.Postgres.Tests;
|
||||
|
||||
[Collection(ExcititorPostgresCollection.Name)]
|
||||
@@ -48,7 +49,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
await _dataSource.DisposeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InsertAndGetById_RoundTripsEvent()
|
||||
{
|
||||
// Arrange
|
||||
@@ -79,7 +81,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
fetched.Attributes.Should().ContainKey("cve");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_ReturnsNullForUnknownEvent()
|
||||
{
|
||||
// Act
|
||||
@@ -89,7 +92,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
result.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetRecentAsync_ReturnsEventsInDescendingOrder()
|
||||
{
|
||||
// Arrange
|
||||
@@ -116,7 +120,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
recent[2].EventId.Should().Be("evt-1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task FindByTraceIdAsync_ReturnsMatchingEvents()
|
||||
{
|
||||
// Arrange
|
||||
@@ -137,7 +142,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
found.Select(e => e.EventId).Should().Contain("evt-a", "evt-b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CountAsync_ReturnsCorrectCount()
|
||||
{
|
||||
// Arrange
|
||||
@@ -151,7 +157,8 @@ public sealed class PostgresVexTimelineEventStoreTests : IAsyncLifetime
|
||||
count.Should().Be(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InsertManyAsync_InsertsMultipleEvents()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -10,11 +10,14 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public class AirgapImportEndpointTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Import_returns_bad_request_when_signature_missing()
|
||||
{
|
||||
var validator = new AirgapImportValidator();
|
||||
@@ -32,7 +35,8 @@ public class AirgapImportEndpointTests
|
||||
Assert.Contains(errors, e => e.Code == "AIRGAP_SIGNATURE_MISSING");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Import_records_actor_and_scope_and_timeline()
|
||||
{
|
||||
var store = new CapturingAirgapStore();
|
||||
@@ -80,7 +84,8 @@ public class AirgapImportEndpointTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Import_returns_remediation_for_sealed_mode_violation()
|
||||
{
|
||||
var store = new CapturingAirgapStore();
|
||||
|
||||
@@ -3,6 +3,7 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class AirgapImportValidatorTests
|
||||
@@ -10,7 +11,8 @@ public sealed class AirgapImportValidatorTests
|
||||
private readonly AirgapImportValidator _validator = new();
|
||||
private readonly DateTimeOffset _now = DateTimeOffset.UtcNow;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenValid_ReturnsEmpty()
|
||||
{
|
||||
var req = new AirgapImportRequest
|
||||
@@ -28,7 +30,8 @@ public sealed class AirgapImportValidatorTests
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_InvalidHash_ReturnsError()
|
||||
{
|
||||
var req = new AirgapImportRequest
|
||||
@@ -46,7 +49,8 @@ public sealed class AirgapImportValidatorTests
|
||||
Assert.Contains(result, e => e.Code == "payload_hash_invalid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_InvalidSignature_ReturnsError()
|
||||
{
|
||||
var req = new AirgapImportRequest
|
||||
@@ -64,7 +68,8 @@ public sealed class AirgapImportValidatorTests
|
||||
Assert.Contains(result, e => e.Code == "AIRGAP_SIGNATURE_INVALID");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_MirrorGenerationNonNumeric_ReturnsError()
|
||||
{
|
||||
var req = new AirgapImportRequest
|
||||
@@ -82,7 +87,8 @@ public sealed class AirgapImportValidatorTests
|
||||
Assert.Contains(result, e => e.Code == "mirror_generation_invalid");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_SignedAtTooOld_ReturnsError()
|
||||
{
|
||||
var req = new AirgapImportRequest
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.WebService.Options;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public class AirgapModeEnforcerTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_WhenNotSealed()
|
||||
{
|
||||
var enforcer = new AirgapModeEnforcer(Microsoft.Extensions.Options.Options.Create(new AirgapOptions { SealedMode = false }), NullLogger<AirgapModeEnforcer>.Instance);
|
||||
@@ -20,7 +22,8 @@ public class AirgapModeEnforcerTests
|
||||
Assert.Null(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Blocks_ExternalUrl_WhenSealed()
|
||||
{
|
||||
var enforcer = new AirgapModeEnforcer(Microsoft.Extensions.Options.Options.Create(new AirgapOptions { SealedMode = true, MirrorOnly = true }), NullLogger<AirgapModeEnforcer>.Instance);
|
||||
@@ -31,7 +34,8 @@ public class AirgapModeEnforcerTests
|
||||
Assert.NotNull(message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Blocks_Untrusted_Publisher_WhenAllowlistSet()
|
||||
{
|
||||
var enforcer = new AirgapModeEnforcer(Microsoft.Extensions.Options.Options.Create(new AirgapOptions { SealedMode = true, TrustedPublishers = { "mirror-a" } }), NullLogger<AirgapModeEnforcer>.Instance);
|
||||
|
||||
@@ -6,11 +6,14 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public class AirgapSignerTrustServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_When_Metadata_Not_Configured()
|
||||
{
|
||||
Environment.SetEnvironmentVariable("STELLAOPS_CONNECTOR_SIGNER_METADATA_PATH", null);
|
||||
@@ -23,7 +26,8 @@ public class AirgapSignerTrustServiceTests
|
||||
Assert.Null(msg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Rejects_When_Publisher_Not_In_Metadata()
|
||||
{
|
||||
using var temp = ConnectorMetadataTempFile();
|
||||
@@ -39,7 +43,8 @@ public class AirgapSignerTrustServiceTests
|
||||
Assert.Contains("missing", msg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Rejects_On_Digest_Mismatch()
|
||||
{
|
||||
using var temp = ConnectorMetadataTempFile();
|
||||
@@ -54,7 +59,8 @@ public class AirgapSignerTrustServiceTests
|
||||
Assert.Equal("AIRGAP_PAYLOAD_MISMATCH", code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Allows_On_Metadata_Match()
|
||||
{
|
||||
using var temp = ConnectorMetadataTempFile();
|
||||
|
||||
@@ -7,12 +7,15 @@ using FluentAssertions;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class AttestationVerifyEndpointTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Verify_ReturnsOk_WhenPayloadValid()
|
||||
{
|
||||
using var factory = new TestWebApplicationFactory(
|
||||
@@ -56,7 +59,8 @@ public sealed class AttestationVerifyEndpointTests
|
||||
body!.Valid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Verify_ReturnsBadRequest_WhenFieldsMissing()
|
||||
{
|
||||
using var factory = new TestWebApplicationFactory(
|
||||
|
||||
@@ -13,6 +13,8 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Options;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class EvidenceLockerEndpointTests : IAsyncLifetime
|
||||
@@ -21,7 +23,8 @@ public sealed class EvidenceLockerEndpointTests : IAsyncLifetime
|
||||
private TestWebApplicationFactory _factory = null!;
|
||||
private StubAirgapImportStore _stubStore = null!;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LockerEndpoint_ReturnsHashesFromLocalFiles_WhenLockerRootConfigured()
|
||||
{
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
@@ -68,7 +71,8 @@ public sealed class EvidenceLockerEndpointTests : IAsyncLifetime
|
||||
Assert.Equal(12, payload.EvidenceSizeBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task LockerManifestFile_StreamsContent_WithETag()
|
||||
{
|
||||
Directory.CreateDirectory(_tempDir);
|
||||
|
||||
@@ -6,11 +6,14 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Telemetry;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class EvidenceTelemetryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordChunkOutcome_EmitsCounterAndHistogram()
|
||||
{
|
||||
var measurements = new List<(string Name, double Value, IReadOnlyList<KeyValuePair<string, object?>> Tags)>();
|
||||
@@ -31,7 +34,8 @@ public sealed class EvidenceTelemetryTests
|
||||
Assert.Equal(true, requestTags["truncated"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RecordChunkSignatureStatus_EmitsSignatureCounters()
|
||||
{
|
||||
var measurements = new List<(string Name, double Value, IReadOnlyList<KeyValuePair<string, object?>> Tags)>();
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.WebService.Options;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class GraphOverlayCacheTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndGet_RoundTripsOverlay()
|
||||
{
|
||||
var memoryCache = new MemoryCache(new MemoryCacheOptions());
|
||||
|
||||
@@ -6,11 +6,13 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.WebService.Graph;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class GraphOverlayFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_EmitsOverlayPerStatementWithProvenance()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -2,11 +2,13 @@ using StellaOps.Excititor.WebService.Contracts;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class GraphOverlayStoreTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task SaveAndFindByPurls_ReturnsLatestPerSourceAdvisory()
|
||||
{
|
||||
var store = new InMemoryGraphOverlayStore();
|
||||
|
||||
@@ -5,11 +5,13 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.WebService.Graph;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class GraphStatusFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_ProjectsStatusCountsPerPurl()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -6,11 +6,13 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.WebService.Graph;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class GraphTooltipFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_OrdersByNewestAndTruncatesPerPurl()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
@@ -64,7 +66,8 @@ public sealed class GraphTooltipFactoryTests
|
||||
Assert.Equal("hash-ubuntu", obs.EvidenceHash);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_UsesLinksetPurlsWhenStatementMissing()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -8,6 +8,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Excititor.WebService.Endpoints;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class IngestEndpointsTests
|
||||
@@ -15,7 +17,8 @@ public sealed class IngestEndpointsTests
|
||||
private readonly FakeIngestOrchestrator _orchestrator = new();
|
||||
private readonly TimeProvider _timeProvider = TimeProvider.System;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InitEndpoint_ReturnsUnauthorized_WhenMissingToken()
|
||||
{
|
||||
var httpContext = CreateHttpContext();
|
||||
@@ -25,7 +28,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.IsType<UnauthorizedHttpResult>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InitEndpoint_ReturnsForbidden_WhenScopeMissing()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.read");
|
||||
@@ -35,7 +39,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.IsType<ForbidHttpResult>(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task InitEndpoint_NormalizesProviders_AndReturnsSummary()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -59,7 +64,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Equal("Initialized 2 provider(s); 1 succeeded, 1 failed.", document.RootElement.GetProperty("message").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunEndpoint_ReturnsBadRequest_WhenSinceInvalid()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -71,7 +77,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Contains("Invalid 'since'", document.RootElement.GetProperty("message").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunEndpoint_ReturnsBadRequest_WhenWindowInvalid()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -83,7 +90,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Contains("Invalid duration", document.RootElement.GetProperty("message").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunEndpoint_PassesOptionsToOrchestrator()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -121,7 +129,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Equal("cp1", document.RootElement.GetProperty("providers")[0].GetProperty("checkpoint").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResumeEndpoint_PassesCheckpointToOrchestrator()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -152,7 +161,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Equal("resume-token", _orchestrator.LastResumeOptions?.Checkpoint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReconcileEndpoint_ReturnsBadRequest_WhenMaxAgeInvalid()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
@@ -164,7 +174,8 @@ public sealed class IngestEndpointsTests
|
||||
Assert.Contains("Invalid duration", document.RootElement.GetProperty("message").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ReconcileEndpoint_PassesOptionsAndReturnsSummary()
|
||||
{
|
||||
var httpContext = CreateHttpContext("vex.admin");
|
||||
|
||||
@@ -11,6 +11,8 @@ using StellaOps.Excititor.Connectors.Abstractions;
|
||||
using StellaOps.Excititor.Export;
|
||||
using StellaOps.Excititor.Policy;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class MirrorEndpointsTests : IDisposable
|
||||
@@ -54,7 +56,8 @@ public sealed class MirrorEndpointsTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ListDomains_ReturnsConfiguredDomain()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -67,7 +70,8 @@ public sealed class MirrorEndpointsTests : IDisposable
|
||||
Assert.Equal("primary", domains[0].GetProperty("id").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task DomainIndex_ReturnsManifestMetadata()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -85,7 +89,8 @@ public sealed class MirrorEndpointsTests : IDisposable
|
||||
Assert.Equal("deadbeef", artifact.GetProperty("digest").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task Download_ReturnsArtifactContent()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
@@ -17,6 +17,8 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.Core.Storage;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class ObservabilityEndpointTests : IDisposable
|
||||
@@ -51,7 +53,8 @@ public sealed class ObservabilityEndpointTests : IDisposable
|
||||
SeedDatabase();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task HealthEndpoint_ReturnsAggregatedMetrics()
|
||||
{
|
||||
var client = _factory.CreateClient(new WebApplicationFactoryClientOptions
|
||||
|
||||
@@ -11,6 +11,7 @@ using StellaOps.Excititor.Connectors.Abstractions;
|
||||
using StellaOps.Excititor.Policy;
|
||||
using StellaOps.Excititor.Core;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -44,7 +45,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WellKnownOpenApi_ReturnsServiceMetadata()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -64,7 +66,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.True(root.TryGetProperty("version", out _), "Response should include version");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenApiSpec_ReturnsValidOpenApi31Document()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -90,7 +93,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.True(paths.TryGetProperty("/excititor/status", out _), "Paths should include /excititor/status");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenApiSpec_IncludesErrorSchemaComponent()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -111,7 +115,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.True(props.TryGetProperty("error", out _), "Error schema should have error property");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenApiSpec_IncludesTimelineEndpoint()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -130,7 +135,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.True(getOp.TryGetProperty("summary", out _), "GET operation should have summary");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenApiSpec_IncludesLinkHeaderExample()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -144,7 +150,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.Contains("describedby", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task WellKnownOpenApi_ContentTypeIsJson()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
@@ -153,7 +160,8 @@ public sealed class OpenApiDiscoveryEndpointTests : IDisposable
|
||||
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task OpenApiSpec_ContentTypeIsJson()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
@@ -5,11 +5,14 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Core.Storage;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class PolicyEndpointsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VexLookup_ReturnsStatements_ForAdvisoryAndPurl()
|
||||
{
|
||||
var claims = CreateSampleClaims();
|
||||
|
||||
@@ -11,6 +11,8 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
using StellaOps.Excititor.Policy;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class ResolveEndpointTests : IDisposable
|
||||
@@ -40,7 +42,8 @@ public sealed class ResolveEndpointTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveEndpoint_ReturnsBadRequest_WhenInputsMissing()
|
||||
{
|
||||
var client = CreateClient("vex.read");
|
||||
@@ -48,7 +51,8 @@ public sealed class ResolveEndpointTests : IDisposable
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveEndpoint_ComputesConsensusAndAttestation()
|
||||
{
|
||||
const string vulnerabilityId = "CVE-2025-2222";
|
||||
@@ -94,7 +98,8 @@ public sealed class ResolveEndpointTests : IDisposable
|
||||
Assert.Equal(providerId, decision.ProviderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveEndpoint_ReturnsConflict_WhenPolicyRevisionMismatch()
|
||||
{
|
||||
const string vulnerabilityId = "CVE-2025-3333";
|
||||
@@ -111,7 +116,8 @@ public sealed class ResolveEndpointTests : IDisposable
|
||||
Assert.Equal(HttpStatusCode.Conflict, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveEndpoint_ReturnsUnauthorized_WhenMissingToken()
|
||||
{
|
||||
var client = CreateClient();
|
||||
@@ -125,7 +131,8 @@ public sealed class ResolveEndpointTests : IDisposable
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ResolveEndpoint_ReturnsForbidden_WhenScopeMissing()
|
||||
{
|
||||
var client = CreateClient("vex.admin");
|
||||
|
||||
@@ -8,6 +8,8 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.Core.RiskFeed;
|
||||
using StellaOps.Excititor.Core.Storage;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
/// <summary>
|
||||
@@ -19,7 +21,8 @@ public sealed class RiskFeedEndpointsTests
|
||||
private const string TestAdvisoryKey = "CVE-2025-1234";
|
||||
private const string TestArtifact = "pkg:maven/org.example/app@1.2.3";
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateFeed_ReturnsItems_ForValidRequest()
|
||||
{
|
||||
var linksets = CreateSampleLinksets();
|
||||
@@ -50,7 +53,8 @@ public sealed class RiskFeedEndpointsTests
|
||||
Assert.Equal(TestAdvisoryKey, body.Items[0].AdvisoryKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GenerateFeed_ReturnsBadRequest_WhenNoBody()
|
||||
{
|
||||
using var factory = new TestWebApplicationFactory(
|
||||
@@ -69,7 +73,8 @@ public sealed class RiskFeedEndpointsTests
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetItem_ReturnsItem_WhenFound()
|
||||
{
|
||||
var linksets = CreateSampleLinksets();
|
||||
@@ -94,7 +99,8 @@ public sealed class RiskFeedEndpointsTests
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetItem_ReturnsNotFound_WhenMissing()
|
||||
{
|
||||
var riskService = new RiskFeedService(new StubLinksetStore(Array.Empty<VexLinkset>()));
|
||||
@@ -117,7 +123,8 @@ public sealed class RiskFeedEndpointsTests
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetFeedByAdvisory_ReturnsItems_ForValidAdvisory()
|
||||
{
|
||||
var linksets = CreateSampleLinksets();
|
||||
|
||||
@@ -12,6 +12,7 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Export;
|
||||
using StellaOps.Excititor.WebService;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class StatusEndpointTests : IDisposable
|
||||
@@ -43,7 +44,8 @@ public sealed class StatusEndpointTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task StatusEndpoint_ReturnsArtifactStores()
|
||||
{
|
||||
var client = _factory.CreateClient();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Excititor.WebService/StellaOps.Excititor.WebService.csproj" />
|
||||
<ProjectReference Include="../../../Aoc/__Libraries/StellaOps.Aoc/StellaOps.Aoc.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
|
||||
@@ -8,6 +8,8 @@ using StellaOps.Excititor.Core;
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexAttestationLinkEndpointTests : IDisposable
|
||||
@@ -31,7 +33,8 @@ public sealed class VexAttestationLinkEndpointTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task GetAttestationLink_ReturnsServiceUnavailable()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
|
||||
@@ -12,6 +12,8 @@ using StellaOps.Excititor.Core;
|
||||
using System.Net;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexEvidenceChunksEndpointTests : IDisposable
|
||||
@@ -35,7 +37,8 @@ public sealed class VexEvidenceChunksEndpointTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunksEndpoint_ReturnsServiceUnavailable_DuringMigration()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
@@ -49,7 +52,8 @@ public sealed class VexEvidenceChunksEndpointTests : IDisposable
|
||||
Assert.Contains("temporarily unavailable", problem, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ChunksEndpoint_ReportsMigrationStatusHeaders()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
|
||||
@@ -4,55 +4,64 @@ using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using StellaOps.Aoc;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexGuardSchemaTests
|
||||
{
|
||||
private static readonly AocWriteGuard Guard = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CycloneDxFixture_CompliesWithGuard()
|
||||
{
|
||||
var result = ValidateCycloneDx();
|
||||
Assert.True(result.IsValid, DescribeViolations(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CsafFixture_CompliesWithGuard()
|
||||
{
|
||||
var result = ValidateCsaf();
|
||||
Assert.True(result.IsValid, DescribeViolations(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CycloneDxFixture_WithForbiddenField_ProducesErrAoc001()
|
||||
{
|
||||
var result = ValidateCycloneDx(node => node["severity"] = "critical");
|
||||
AssertViolation(result, "ERR_AOC_001", "/severity");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CycloneDxFixture_WithDerivedField_ProducesErrAoc006()
|
||||
{
|
||||
var result = ValidateCycloneDx(node => node["effective_owner"] = "security");
|
||||
AssertViolation(result, "ERR_AOC_006", "/effective_owner");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CycloneDxFixture_WithUnknownField_ProducesErrAoc007()
|
||||
{
|
||||
var result = ValidateCycloneDx(node => node["custom_field"] = 123);
|
||||
AssertViolation(result, "ERR_AOC_007", "/custom_field");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CycloneDxFixture_WithSupersedes_RemainsValid()
|
||||
{
|
||||
var result = ValidateCycloneDx(node => node["supersedes"] = "digest:prev-cdx");
|
||||
Assert.True(result.IsValid, DescribeViolations(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void CsafFixture_WithSupersedes_RemainsValid()
|
||||
{
|
||||
var result = ValidateCsaf(node => node["supersedes"] = "digest:prev-csaf");
|
||||
|
||||
@@ -13,6 +13,8 @@ using StellaOps.Excititor.Core.Storage;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexLinksetListEndpointTests : IDisposable
|
||||
@@ -38,7 +40,8 @@ public sealed class VexLinksetListEndpointTests : IDisposable
|
||||
SeedObservations();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async void LinksetsEndpoint_GroupsByVulnAndProduct()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
|
||||
@@ -13,6 +13,8 @@ using StellaOps.Excititor.Core.Observations;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexObservationListEndpointTests : IDisposable
|
||||
@@ -38,7 +40,8 @@ public sealed class VexObservationListEndpointTests : IDisposable
|
||||
SeedObservation();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ObservationsEndpoint_ReturnsFilteredResults()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
|
||||
|
||||
@@ -10,11 +10,13 @@ using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.WebService.Services;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexObservationProjectionServiceTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_FiltersByProviderAndStatus()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 11, 10, 12, 0, 0, TimeSpan.Zero);
|
||||
@@ -49,7 +51,8 @@ public sealed class VexObservationProjectionServiceTests
|
||||
statement.Document.Digest.Should().Contain("provider-b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task QueryAsync_TruncatesWhenLimitExceeded()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
@@ -9,6 +9,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Excititor.WebService.Contracts;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.WebService.Tests;
|
||||
|
||||
public sealed class VexRawEndpointsTests
|
||||
@@ -32,7 +34,8 @@ public sealed class VexRawEndpointsTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task IngestListGetAndVerifyFlow()
|
||||
{
|
||||
using var client = _factory.CreateClient(new WebApplicationFactoryClientOptions
|
||||
|
||||
@@ -21,12 +21,14 @@ using StellaOps.Excititor.Worker.Signature;
|
||||
using StellaOps.Plugin;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Worker.Tests;
|
||||
|
||||
public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_LargeBatch_IdempotentAcrossRestart()
|
||||
{
|
||||
var specs = CreateDocumentSpecs(count: 48);
|
||||
@@ -95,7 +97,8 @@ public sealed class DefaultVexProviderRunnerIntegrationTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenGuardFails_RestartCompletesSuccessfully()
|
||||
{
|
||||
var specs = CreateDocumentSpecs(count: 24);
|
||||
|
||||
@@ -28,13 +28,15 @@ using Xunit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using StellaOps.IssuerDirectory.Client;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Worker.Tests;
|
||||
|
||||
public sealed class DefaultVexProviderRunnerTests
|
||||
{
|
||||
private static readonly VexConnectorSettings EmptySettings = VexConnectorSettings.Empty;
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_Skips_WhenNextEligibleRunInFuture()
|
||||
{
|
||||
var time = new FixedTimeProvider(new DateTimeOffset(2025, 10, 21, 15, 0, 0, TimeSpan.Zero));
|
||||
@@ -67,7 +69,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
state.NextEligibleRun.Should().Be(time.GetUtcNow().AddHours(1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_Success_ResetsFailureCounters()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 16, 0, 0, TimeSpan.Zero);
|
||||
@@ -103,7 +106,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
state.LastSuccessAt.Should().Be(now);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_UsesStoredResumeTokens()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 18, 0, 0, TimeSpan.Zero);
|
||||
@@ -137,7 +141,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
connector.LastContext.ResumeTokens.Should().BeEquivalentTo(resumeTokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_SchedulesRefresh_ForUniqueClaims()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 19, 0, 0, TimeSpan.Zero);
|
||||
@@ -182,7 +187,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
normalizer.CallCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_WhenSignatureVerifierFails_PropagatesException()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 20, 0, 0, TimeSpan.Zero);
|
||||
@@ -223,7 +229,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
rawStore.StoreCallCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_EnrichesMetadataWithSignatureResult()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 21, 0, 0, TimeSpan.Zero);
|
||||
@@ -276,7 +283,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
signatureVerifier.Invocations.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_Attestation_StoresVerifierMetadata()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 28, 7, 0, 0, TimeSpan.Zero);
|
||||
@@ -328,7 +336,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
attestationVerifier.Invocations.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_Failure_AppliesBackoff()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 17, 0, 0, TimeSpan.Zero);
|
||||
@@ -366,7 +375,8 @@ public sealed class DefaultVexProviderRunnerTests
|
||||
state.NextEligibleRun.Should().Be(now + TimeSpan.FromMinutes(10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task RunAsync_NonRetryableFailure_AppliesQuarantine()
|
||||
{
|
||||
var now = new DateTimeOffset(2025, 10, 21, 17, 0, 0, TimeSpan.Zero);
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Excititor.Worker/StellaOps.Excititor.Worker.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Excititor.Storage.Postgres/StellaOps.Excititor.Storage.Postgres.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,11 +6,14 @@ using StellaOps.Excititor.Worker.Auth;
|
||||
using StellaOps.Excititor.Worker.Options;
|
||||
using Xunit;
|
||||
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Worker.Tests;
|
||||
|
||||
public sealed class TenantAuthorityClientFactoryTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_WhenTenantConfigured_SetsBaseAddressAndTenantHeader()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
@@ -24,7 +27,8 @@ public sealed class TenantAuthorityClientFactoryTests
|
||||
values.Should().ContainSingle().Which.Should().Be("tenant-a");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_Throws_WhenTenantMissing()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
@@ -35,7 +39,8 @@ public sealed class TenantAuthorityClientFactoryTests
|
||||
.Should().Throw<ArgumentException>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Create_Throws_WhenTenantNotConfigured()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
|
||||
@@ -3,13 +3,15 @@ using Microsoft.Extensions.Options;
|
||||
using StellaOps.Excititor.Worker.Options;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Worker.Tests;
|
||||
|
||||
public sealed class TenantAuthorityOptionsValidatorTests
|
||||
{
|
||||
private readonly TenantAuthorityOptionsValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Fails_When_BaseUrls_Empty()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
@@ -19,7 +21,8 @@ public sealed class TenantAuthorityOptionsValidatorTests
|
||||
result.Failed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Fails_When_Key_Or_Value_Blank()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
@@ -30,7 +33,8 @@ public sealed class TenantAuthorityOptionsValidatorTests
|
||||
result.Failed.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_Succeeds_When_Valid()
|
||||
{
|
||||
var options = new TenantAuthorityOptions();
|
||||
|
||||
@@ -4,11 +4,13 @@ using StellaOps.Excititor.Worker.Options;
|
||||
using StellaOps.Excititor.Worker.Scheduling;
|
||||
using Xunit;
|
||||
|
||||
using StellaOps.TestKit;
|
||||
namespace StellaOps.Excititor.Worker.Tests;
|
||||
|
||||
public sealed class VexWorkerOptionsTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveSchedules_UsesDefaultIntervalWhenNotSpecified()
|
||||
{
|
||||
var options = new VexWorkerOptions
|
||||
@@ -26,7 +28,8 @@ public sealed class VexWorkerOptionsTests
|
||||
schedules[0].Settings.Should().Be(VexConnectorSettings.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveSchedules_HonorsOfflineInterval()
|
||||
{
|
||||
var options = new VexWorkerOptions
|
||||
@@ -43,7 +46,8 @@ public sealed class VexWorkerOptionsTests
|
||||
schedules[0].Interval.Should().Be(TimeSpan.FromHours(8));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveSchedules_SkipsDisabledProviders()
|
||||
{
|
||||
var options = new VexWorkerOptions();
|
||||
@@ -56,7 +60,8 @@ public sealed class VexWorkerOptionsTests
|
||||
schedules[0].ProviderId.Should().Be("excititor:enabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void ResolveSchedules_UsesProviderIntervalOverride()
|
||||
{
|
||||
var options = new VexWorkerOptions
|
||||
@@ -77,7 +82,8 @@ public sealed class VexWorkerOptionsTests
|
||||
schedules[0].InitialDelay.Should().Be(TimeSpan.FromSeconds(10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void RefreshOptions_DefaultsAlignWithExpectedValues()
|
||||
{
|
||||
var options = new VexWorkerRefreshOptions();
|
||||
@@ -88,7 +94,8 @@ public sealed class VexWorkerOptionsTests
|
||||
options.Damper.ResolveDuration(0.6).Should().Be(TimeSpan.FromHours(36));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DamperOptions_ClampDurationWithinBounds()
|
||||
{
|
||||
var options = new VexStabilityDamperOptions
|
||||
|
||||
Reference in New Issue
Block a user