Refactor code structure and optimize performance across multiple modules

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

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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" />

View File

@@ -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

View File

@@ -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(

View File

@@ -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>();

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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);

View File

@@ -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");

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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(

View File

@@ -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(

View File

@@ -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 = """

View File

@@ -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();

View File

@@ -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");

View File

@@ -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)]

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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(

View File

@@ -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);

View File

@@ -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(

View File

@@ -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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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");

View File

@@ -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();

View File

@@ -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();

View File

@@ -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>

View File

@@ -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();

View File

@@ -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(

View File

@@ -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 = """

View File

@@ -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" />

View File

@@ -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(

View File

@@ -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(

View File

@@ -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 = """

View File

@@ -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>

View File

@@ -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(

View File

@@ -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 = """

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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";

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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(

View File

@@ -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);

View File

@@ -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)>();

View File

@@ -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());

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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");

View File

@@ -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();

View File

@@ -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

View File

@@ -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();

View File

@@ -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();

View File

@@ -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");

View File

@@ -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();

View File

@@ -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();

View File

@@ -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" />

View File

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

View File

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

View File

@@ -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");

View File

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

View File

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

View File

@@ -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;

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>

View File

@@ -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();

View File

@@ -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();

View File

@@ -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