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

@@ -8,6 +8,7 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
/// <summary>
@@ -15,7 +16,8 @@ namespace StellaOps.Signals.Tests;
/// </summary>
public sealed class CallGraphSyncServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_WithValidDocument_ReturnsSuccessResult()
{
// Arrange
@@ -40,7 +42,8 @@ public sealed class CallGraphSyncServiceTests
Assert.True(result.DurationMs >= 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_ProjectsToRepository()
{
// Arrange
@@ -63,7 +66,8 @@ public sealed class CallGraphSyncServiceTests
Assert.Single(repository.Entrypoints);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_SetsScanStatusToCompleted()
{
// Arrange
@@ -84,7 +88,8 @@ public sealed class CallGraphSyncServiceTests
Assert.Equal("completed", repository.Scans[scanId].Status);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_WithEmptyDocument_ReturnsZeroCounts()
{
// Arrange
@@ -115,7 +120,8 @@ public sealed class CallGraphSyncServiceTests
Assert.False(result.WasUpdated);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_WithNullDocument_ThrowsArgumentNullException()
{
// Arrange
@@ -130,7 +136,8 @@ public sealed class CallGraphSyncServiceTests
service.SyncAsync(Guid.NewGuid(), "sha256:test-digest", null!));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_WithEmptyArtifactDigest_ThrowsArgumentException()
{
// Arrange
@@ -147,7 +154,8 @@ public sealed class CallGraphSyncServiceTests
service.SyncAsync(Guid.NewGuid(), "", document));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task DeleteByScanAsync_RemovesScanFromRepository()
{
// Arrange
@@ -171,7 +179,8 @@ public sealed class CallGraphSyncServiceTests
Assert.Empty(repository.Entrypoints);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task SyncAsync_OrdersNodesAndEdgesDeterministically()
{
// Arrange

View File

@@ -17,6 +17,8 @@ using StellaOps.Signals.Storage;
using StellaOps.Signals.Storage.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class CallgraphIngestionServiceTests
@@ -26,7 +28,8 @@ public class CallgraphIngestionServiceTests
private readonly CallgraphNormalizationService _normalizer = new();
private readonly TimeProvider _timeProvider = TimeProvider.System;
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_normalizes_graph_and_persists_manifest_hash()
{
var parser = new StubParser("java");

View File

@@ -6,13 +6,15 @@ using StellaOps.Signals.Parsing;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class CallgraphNormalizationServiceTests
{
private readonly CallgraphNormalizationService _service = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_adds_language_and_namespace_for_java()
{
var result = new CallgraphParseResult(
@@ -36,7 +38,8 @@ public class CallgraphNormalizationServiceTests
node.Name.Should().Be("com/example/Foo.bar:(I)V");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_deduplicates_edges_and_clamps_confidence()
{
var result = new CallgraphParseResult(
@@ -66,7 +69,8 @@ public class CallgraphNormalizationServiceTests
edge.Evidence.Should().BeEquivalentTo(new[] { "x" });
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Normalize_normalizes_gate_metadata()
{
var result = new CallgraphParseResult(

View File

@@ -9,6 +9,8 @@ using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class EdgeBundleIngestionServiceTests
@@ -25,7 +27,8 @@ public class EdgeBundleIngestionServiceTests
_service = new EdgeBundleIngestionService(NullLogger<EdgeBundleIngestionService>.Instance, options);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_ParsesBundleAndStoresDocument()
{
// Arrange
@@ -58,7 +61,8 @@ public class EdgeBundleIngestionServiceTests
Assert.Contains("cas://reachability/edges/", result.CasUri);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_TracksRevokedEdgesForQuarantine()
{
// Arrange
@@ -87,7 +91,8 @@ public class EdgeBundleIngestionServiceTests
Assert.True(result.Quarantined);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IsEdgeRevokedAsync_ReturnsTrueForRevokedEdges()
{
// Arrange
@@ -110,7 +115,8 @@ public class EdgeBundleIngestionServiceTests
Assert.False(await _service.IsEdgeRevokedAsync(TestTenantId, TestGraphHash, "other_func", "some_func"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetBundlesForGraphAsync_ReturnsAllBundlesForGraph()
{
// Arrange - ingest multiple bundles
@@ -134,7 +140,8 @@ public class EdgeBundleIngestionServiceTests
Assert.Contains(bundles, b => b.BundleId == "bundle:2");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task GetRevokedEdgesAsync_ReturnsOnlyRevokedEdges()
{
// Arrange
@@ -162,7 +169,8 @@ public class EdgeBundleIngestionServiceTests
Assert.All(revokedEdges, e => Assert.True(e.Revoked));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_WithDsseStream_SetsVerifiedAndDsseFields()
{
// Arrange
@@ -191,7 +199,8 @@ public class EdgeBundleIngestionServiceTests
Assert.EndsWith(".dsse", result.DsseCasUri);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_ThrowsOnMissingGraphHash()
{
// Arrange
@@ -207,7 +216,8 @@ public class EdgeBundleIngestionServiceTests
await Assert.ThrowsAsync<InvalidOperationException>(() => _service.IngestAsync(TestTenantId, stream, null));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_UpdatesExistingBundleWithSameId()
{
// Arrange

View File

@@ -7,11 +7,13 @@ using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class InMemoryEventsPublisherTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishFactUpdatedAsync_EmitsStructuredEvent()
{
var logger = new TestLogger<InMemoryEventsPublisher>();

View File

@@ -4,9 +4,11 @@ using StellaOps.Signals.Models;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
public class ReachabilityFactDigestCalculatorTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Compute_ReturnsDeterministicDigest_ForEquivalentFacts()
{
var factA = new ReachabilityFactDocument

View File

@@ -1,11 +1,13 @@
using StellaOps.Signals.Lattice;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class ReachabilityLatticeTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ReachabilityLatticeState.Unknown, ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.StaticallyReachable)]
[InlineData(ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.StaticallyUnreachable, ReachabilityLatticeState.Contested)]
[InlineData(ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.RuntimeObserved, ReachabilityLatticeState.ConfirmedReachable)]
@@ -19,7 +21,8 @@ public class ReachabilityLatticeTests
Assert.Equal(expected, result);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ReachabilityLatticeState.Unknown, ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.Unknown)]
[InlineData(ReachabilityLatticeState.ConfirmedReachable, ReachabilityLatticeState.RuntimeObserved, ReachabilityLatticeState.RuntimeObserved)]
[InlineData(ReachabilityLatticeState.Contested, ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.StaticallyReachable)]
@@ -30,7 +33,8 @@ public class ReachabilityLatticeTests
Assert.Equal(expected, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Join_IsCommutative()
{
var states = Enum.GetValues<ReachabilityLatticeState>();
@@ -43,7 +47,8 @@ public class ReachabilityLatticeTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Meet_IsCommutative()
{
var states = Enum.GetValues<ReachabilityLatticeState>();
@@ -56,14 +61,16 @@ public class ReachabilityLatticeTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void JoinAll_WithEmptySequence_ReturnsUnknown()
{
var result = ReachabilityLattice.JoinAll(Array.Empty<ReachabilityLatticeState>());
Assert.Equal(ReachabilityLatticeState.Unknown, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void JoinAll_StopsEarlyOnContested()
{
var states = new[] { ReachabilityLatticeState.StaticallyReachable, ReachabilityLatticeState.Contested, ReachabilityLatticeState.Unknown };
@@ -71,7 +78,8 @@ public class ReachabilityLatticeTests
Assert.Equal(ReachabilityLatticeState.Contested, result);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(true, false, false, ReachabilityLatticeState.StaticallyReachable)]
[InlineData(false, false, false, ReachabilityLatticeState.StaticallyUnreachable)]
[InlineData(null, false, false, ReachabilityLatticeState.Unknown)]
@@ -84,7 +92,8 @@ public class ReachabilityLatticeTests
Assert.Equal(expected, result);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("entrypoint", false, ReachabilityLatticeState.ConfirmedReachable)]
[InlineData("direct", false, ReachabilityLatticeState.StaticallyReachable)]
[InlineData("direct", true, ReachabilityLatticeState.ConfirmedReachable)]
@@ -101,7 +110,8 @@ public class ReachabilityLatticeTests
public class ReachabilityLatticeStateExtensionsTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ReachabilityLatticeState.Unknown, "U")]
[InlineData(ReachabilityLatticeState.StaticallyReachable, "SR")]
[InlineData(ReachabilityLatticeState.StaticallyUnreachable, "SU")]
@@ -115,7 +125,8 @@ public class ReachabilityLatticeStateExtensionsTests
Assert.Equal(expectedCode, state.ToCode());
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("U", ReachabilityLatticeState.Unknown)]
[InlineData("SR", ReachabilityLatticeState.StaticallyReachable)]
[InlineData("SU", ReachabilityLatticeState.StaticallyUnreachable)]
@@ -132,7 +143,8 @@ public class ReachabilityLatticeStateExtensionsTests
Assert.Equal(expected, ReachabilityLatticeStateExtensions.FromCode(code));
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(ReachabilityLatticeState.ConfirmedUnreachable, "unreachable")]
[InlineData(ReachabilityLatticeState.StaticallyUnreachable, "unreachable")]
[InlineData(ReachabilityLatticeState.RuntimeUnobserved, "unreachable")]

View File

@@ -9,9 +9,11 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
public class ReachabilityScoringServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecomputeAsync_applies_gate_multipliers_and_surfaces_gate_evidence()
{
var callgraph = new CallgraphDocument
@@ -92,7 +94,8 @@ public class ReachabilityScoringServiceTests
Assert.Equal(0.204, fact.RiskScore, 3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecomputeAsync_UsesConfiguredWeights()
{
var callgraph = new CallgraphDocument
@@ -169,7 +172,8 @@ public class ReachabilityScoringServiceTests
Assert.False(string.IsNullOrWhiteSpace(fact.Metadata?["fact.digest"]));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RecomputeAsync_ComputesUncertaintyRiskScoreUsingConfiguredEntropyWeights()
{
var callgraph = new CallgraphDocument

View File

@@ -8,11 +8,14 @@ using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class ReachabilityUnionIngestionServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_ValidBundle_WritesFilesAndValidatesHashes()
{
// Arrange

View File

@@ -11,11 +11,14 @@ using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class RouterEventsPublisherTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishFactUpdatedAsync_SendsEnvelopeToRouter()
{
var options = CreateOptions();
@@ -38,7 +41,8 @@ public class RouterEventsPublisherTests
Assert.Contains(logger.Messages, m => m.Contains("Router publish succeeded"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task PublishFactUpdatedAsync_LogsFailure()
{
var options = CreateOptions();

View File

@@ -10,6 +10,8 @@ using StellaOps.Signals.Storage;
using StellaOps.Signals.Storage.Models;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class RuntimeFactsBatchIngestionTests
@@ -17,7 +19,8 @@ public class RuntimeFactsBatchIngestionTests
private const string TestTenantId = "test-tenant";
private const string TestCallgraphId = "test-callgraph-123";
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_ParsesNdjsonAndStoresArtifact()
{
// Arrange
@@ -49,7 +52,8 @@ public class RuntimeFactsBatchIngestionTests
Assert.True(artifactStore.StoredArtifacts.Count > 0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_HandlesGzipCompressedContent()
{
// Arrange
@@ -81,7 +85,8 @@ public class RuntimeFactsBatchIngestionTests
Assert.Equal(10, result.TotalHitCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_GroupsEventsBySubject()
{
// Arrange
@@ -111,7 +116,8 @@ public class RuntimeFactsBatchIngestionTests
Assert.Contains("scan-2", result.SubjectKeys);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_LinksCasUriToFactDocument()
{
// Arrange
@@ -137,7 +143,8 @@ public class RuntimeFactsBatchIngestionTests
Assert.Equal(result.BatchHash, fact.RuntimeFactsBatchHash);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_SkipsInvalidLines()
{
// Arrange
@@ -163,7 +170,8 @@ public class RuntimeFactsBatchIngestionTests
Assert.Equal(3, result.TotalHitCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestBatchAsync_WorksWithoutArtifactStore()
{
// Arrange

View File

@@ -10,9 +10,11 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
public class RuntimeFactsIngestionServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_AggregatesHits_AndRecomputesReachability()
{
var factRepository = new InMemoryReachabilityFactRepository();
@@ -165,7 +167,8 @@ public class RuntimeFactsIngestionServiceTests
#region Tenant Isolation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_IsolatesFactsBySubjectKey_NoDataLeakBetweenTenants()
{
// Arrange: Two tenants with different subjects
@@ -209,7 +212,8 @@ public class RuntimeFactsIngestionServiceTests
tenant2Facts.RuntimeFacts.Should().NotContain(f => f.SymbolId == "tenant1.secret.func");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_SubjectKeyIsDeterministic_ForSameInput()
{
// Arrange
@@ -240,7 +244,8 @@ public class RuntimeFactsIngestionServiceTests
response1.SubjectKey.Should().Be("mylib|1.0.0");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_BuildIdCorrelation_PreservesPerFactBuildId()
{
// Arrange
@@ -283,7 +288,8 @@ public class RuntimeFactsIngestionServiceTests
cryptoFact.BuildId.Should().Be("gnu-build-id:a1b2c3d4e5f6");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_CodeIdCorrelation_PreservesPerFactCodeId()
{
// Arrange
@@ -315,7 +321,8 @@ public class RuntimeFactsIngestionServiceTests
persisted.RuntimeFacts[0].CodeId.Should().Be("code:binary:abc123xyz");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_RejectsRequest_WhenSubjectMissing()
{
// Arrange
@@ -333,7 +340,8 @@ public class RuntimeFactsIngestionServiceTests
() => service.IngestAsync(request, CancellationToken.None));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_RejectsRequest_WhenCallgraphIdMissing()
{
// Arrange
@@ -351,7 +359,8 @@ public class RuntimeFactsIngestionServiceTests
() => service.IngestAsync(request, CancellationToken.None));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_RejectsRequest_WhenEventsEmpty()
{
// Arrange
@@ -369,7 +378,8 @@ public class RuntimeFactsIngestionServiceTests
() => service.IngestAsync(request, CancellationToken.None));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_RejectsRequest_WhenEventMissingSymbolId()
{
// Arrange
@@ -394,7 +404,8 @@ public class RuntimeFactsIngestionServiceTests
#region Evidence URI Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_PreservesEvidenceUri_FromRuntimeEvent()
{
// Arrange

View File

@@ -5,13 +5,15 @@ using StellaOps.Signals.Models;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class RuntimeFactsProvenanceNormalizerTests
{
private readonly RuntimeFactsProvenanceNormalizer _normalizer = new();
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_CreatesValidProvenanceFeed()
{
var events = new List<RuntimeFactEvent>
@@ -33,7 +35,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(2, feed.Records.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_PopulatesAocMetadata()
{
var events = new List<RuntimeFactEvent>
@@ -53,7 +56,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("ebpf-agent", feed.Metadata["request.source"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SetsRecordTypeBasedOnProcessMetadata()
{
var evt = new RuntimeFactEvent
@@ -72,7 +76,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(ProvenanceSubjectType.Process, feed.Records[0].Subject.Type);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SetsRecordTypeForNetworkConnection()
{
var evt = new RuntimeFactEvent
@@ -89,7 +94,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("runtime.network.connection", feed.Records[0].RecordType);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SetsRecordTypeForContainerActivity()
{
var evt = new RuntimeFactEvent
@@ -107,7 +113,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(ProvenanceSubjectType.Container, feed.Records[0].Subject.Type);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SetsRecordTypeForPackageLoaded()
{
var evt = new RuntimeFactEvent
@@ -126,7 +133,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("pkg:npm/lodash@4.17.21", feed.Records[0].Subject.Identifier);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_PopulatesRuntimeProvenanceFacts()
{
var evt = new RuntimeFactEvent
@@ -164,7 +172,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("prod", facts.Metadata["env"]);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SetsConfidenceBasedOnEvidence()
{
var evtWithFullEvidence = new RuntimeFactEvent
@@ -194,7 +203,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.True(minimalRecord.Confidence >= 0.95);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_BuildsEvidenceWithCaptureMethod()
{
var evt = new RuntimeFactEvent
@@ -217,7 +227,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("s3://evidence/trace.json", evidence.RawDataRef);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_NormalizesDigestWithSha256Prefix()
{
var evt = new RuntimeFactEvent
@@ -236,7 +247,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.StartsWith("sha256:", evidence.SourceDigest);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_SkipsEventsWithEmptySymbolId()
{
var events = new List<RuntimeFactEvent>
@@ -254,7 +266,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("valid.symbol", feed.Records[0].Facts?.SymbolId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateContextFacts_ReturnsPopulatedContextFacts()
{
var events = new List<RuntimeFactEvent>
@@ -275,7 +288,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(3, contextFacts.Provenance.Records.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_DeterminesObserverFromContainerContext()
{
var evt = new RuntimeFactEvent
@@ -292,7 +306,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("container-runtime-agent", feed.Records[0].ObservedBy);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_DeterminesObserverFromProcessContext()
{
var evt = new RuntimeFactEvent
@@ -309,7 +324,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("process-monitor-agent", feed.Records[0].ObservedBy);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_UsesObservedAtFromEvent()
{
var observedTime = DateTimeOffset.Parse("2025-12-06T08:00:00Z");
@@ -328,7 +344,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(observedTime, feed.Records[0].OccurredAt);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_FallsBackToGeneratedAtWhenNoObservedAt()
{
var generatedTime = DateTimeOffset.Parse("2025-12-07T10:00:00Z");
@@ -345,7 +362,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal(generatedTime, feed.Records[0].OccurredAt);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_BuildsSubjectIdentifierFromPurl()
{
var evt = new RuntimeFactEvent
@@ -362,7 +380,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("pkg:npm/express@4.18.0", feed.Records[0].Subject.Identifier);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_BuildsSubjectIdentifierFromComponent()
{
var evt = new RuntimeFactEvent
@@ -378,7 +397,8 @@ public class RuntimeFactsProvenanceNormalizerTests
Assert.Equal("my-service@2.0.0", feed.Records[0].Subject.Identifier);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void NormalizeToFeed_UsesImageDigestAsSubjectForContainers()
{
var evt = new RuntimeFactEvent

View File

@@ -4,6 +4,7 @@ using StellaOps.Signals.Models;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class SchedulerRescanOrchestratorTests
@@ -19,7 +20,8 @@ public class SchedulerRescanOrchestratorTests
_sut = new SchedulerRescanOrchestrator(_mockClient, _timeProvider, _logger);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_CreatesJobWithCorrectPriority_Immediate()
{
// Arrange
@@ -35,7 +37,8 @@ public class SchedulerRescanOrchestratorTests
_mockClient.LastRequest.PackageUrl.Should().Be("pkg:npm/lodash@4.17.21");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_CreatesJobWithCorrectPriority_Scheduled()
{
// Arrange
@@ -49,7 +52,8 @@ public class SchedulerRescanOrchestratorTests
_mockClient.LastRequest!.Priority.Should().Be(RescanJobPriority.Normal);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_CreatesJobWithCorrectPriority_Batch()
{
// Arrange
@@ -63,7 +67,8 @@ public class SchedulerRescanOrchestratorTests
_mockClient.LastRequest!.Priority.Should().Be(RescanJobPriority.Low);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_PropagatesCorrelationId()
{
// Arrange
@@ -78,7 +83,8 @@ public class SchedulerRescanOrchestratorTests
_mockClient.LastRequest.TenantId.Should().Be("tenant123");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_ReturnsNextScheduledRescan_ForImmediate()
{
// Arrange
@@ -93,7 +99,8 @@ public class SchedulerRescanOrchestratorTests
result.NextScheduledRescan.Should().Be(now.AddMinutes(15));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_ReturnsNextScheduledRescan_ForScheduled()
{
// Arrange
@@ -108,7 +115,8 @@ public class SchedulerRescanOrchestratorTests
result.NextScheduledRescan.Should().Be(now.AddHours(24));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_ReturnsNextScheduledRescan_ForBatch()
{
// Arrange
@@ -123,7 +131,8 @@ public class SchedulerRescanOrchestratorTests
result.NextScheduledRescan.Should().Be(now.AddDays(7));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_ReturnsFailure_WhenClientFails()
{
// Arrange
@@ -139,7 +148,8 @@ public class SchedulerRescanOrchestratorTests
result.ErrorMessage.Should().Be("Queue unavailable");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerBatchRescanAsync_ProcessesAllItems()
{
// Arrange
@@ -160,7 +170,8 @@ public class SchedulerRescanOrchestratorTests
result.Results.Should().HaveCount(3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerBatchRescanAsync_EmptyList_ReturnsEmpty()
{
// Arrange
@@ -175,7 +186,8 @@ public class SchedulerRescanOrchestratorTests
result.FailureCount.Should().Be(0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_ExtractsTenantFromCallgraphId()
{
// Arrange
@@ -189,7 +201,8 @@ public class SchedulerRescanOrchestratorTests
_mockClient.LastRequest!.TenantId.Should().Be("acme-corp");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task TriggerRescanAsync_UsesDefaultTenant_WhenNoCallgraphId()
{
// Arrange

View File

@@ -11,6 +11,7 @@ using StellaOps.Signals.Options;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class ScoreExplanationServiceTests
@@ -26,7 +27,8 @@ public class ScoreExplanationServiceTests
NullLogger<ScoreExplanationService>.Instance);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithCvssOnly_ReturnsCorrectContribution()
{
var request = new ScoreExplanationRequest
@@ -47,7 +49,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(50.0, result.RiskScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithEpss_ReturnsCorrectContribution()
{
var request = new ScoreExplanationRequest
@@ -63,7 +66,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(5.0, epssContrib.Contribution); // 0.5 * 10.0 default multiplier
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("entrypoint", 25.0)]
[InlineData("direct", 20.0)]
[InlineData("runtime", 22.0)]
@@ -83,7 +87,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(expectedContribution, reachContrib.Contribution);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("http", 15.0)]
[InlineData("https", 15.0)]
[InlineData("http_handler", 15.0)]
@@ -104,7 +109,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(expectedContribution, exposureContrib.Contribution);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithAuthGate_AppliesDiscount()
{
var request = new ScoreExplanationRequest
@@ -120,7 +126,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(37.0, result.RiskScore); // 8.0 * 5.0 - 3.0
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithMultipleGates_CombinesDiscounts()
{
var request = new ScoreExplanationRequest
@@ -137,7 +144,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(40.0, result.RiskScore); // 50 - 10
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithKev_AppliesBonus()
{
var request = new ScoreExplanationRequest
@@ -153,7 +161,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(45.0, result.RiskScore); // 7.0 * 5.0 + 10.0
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_WithVexNotAffected_ReducesScore()
{
var request = new ScoreExplanationRequest
@@ -169,7 +178,8 @@ public class ScoreExplanationServiceTests
Assert.True(result.RiskScore < 50.0); // Should be significantly reduced
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_ClampsToMaxScore()
{
var request = new ScoreExplanationRequest
@@ -188,7 +198,8 @@ public class ScoreExplanationServiceTests
Assert.Contains(result.Modifiers, m => m.Type == "cap");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_ContributionsSumToTotal()
{
var request = new ScoreExplanationRequest
@@ -205,7 +216,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(expectedSum, result.RiskScore, precision: 5);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_GeneratesSummary()
{
var request = new ScoreExplanationRequest
@@ -220,7 +232,8 @@ public class ScoreExplanationServiceTests
Assert.Contains("risk", result.Summary, StringComparison.OrdinalIgnoreCase);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_SetsAlgorithmVersion()
{
var request = new ScoreExplanationRequest { CvssScore = 5.0 };
@@ -230,7 +243,8 @@ public class ScoreExplanationServiceTests
Assert.Equal("1.0.0", result.AlgorithmVersion);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_PreservesEvidenceRef()
{
var request = new ScoreExplanationRequest
@@ -244,7 +258,8 @@ public class ScoreExplanationServiceTests
Assert.Equal("scan:abc123", result.EvidenceRef);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ComputeExplanationAsync_ReturnsSameAsSync()
{
var request = new ScoreExplanationRequest
@@ -260,7 +275,8 @@ public class ScoreExplanationServiceTests
Assert.Equal(syncResult.Contributions.Count, asyncResult.Contributions.Count);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ComputeExplanation_IsDeterministic()
{
var request = new ScoreExplanationRequest

View File

@@ -7,11 +7,14 @@ using StellaOps.Signals.Models;
using StellaOps.Signals.Parsing;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public sealed class SimpleJsonCallgraphParserGateTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ParseAsync_parses_gate_fields_on_edges()
{
var json = """

View File

@@ -1,6 +1,7 @@
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
/// <summary>
@@ -35,7 +36,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Add_ShouldAddSymbolsToCache()
{
// Arrange
@@ -53,7 +55,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.True(_cache.Contains(buildId));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryResolve_ShouldResolveKnownAddress()
{
// Arrange
@@ -75,7 +78,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Equal(buildId, symbol.BuildId);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryResolve_ShouldReturnFalseForUnknownBuildId()
{
// Act
@@ -86,7 +90,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Null(symbol);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryResolve_ShouldReturnFalseForAddressOutsideSymbols()
{
// Arrange
@@ -105,7 +110,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Null(symbol);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TryResolve_ShouldUseBinarySearchForLargeSymbolTable()
{
// Arrange
@@ -131,7 +137,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Equal(25UL, symbol.Offset);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void GetStatistics_ShouldReturnCorrectStats()
{
// Arrange
@@ -157,7 +164,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Equal(0.5, stats.HitRate);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Clear_ShouldRemoveAllEntries()
{
// Arrange
@@ -178,7 +186,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.Equal(0, stats.EntryCount);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Remove_ShouldRemoveSpecificEntry()
{
// Arrange
@@ -198,7 +207,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.True(_cache.Contains("build2"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Persistence_ShouldWriteToDisk()
{
// Arrange
@@ -215,7 +225,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
Assert.NotEmpty(files);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void TrustedSymbols_ShouldBeFlaggedCorrectly()
{
// Arrange
@@ -239,7 +250,8 @@ public sealed class SlimSymbolCacheTests : IDisposable
/// </summary>
public sealed class CanonicalSymbolTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ToCanonicalString_ShouldFormatCorrectly()
{
// Arrange
@@ -258,7 +270,8 @@ public sealed class CanonicalSymbolTests
Assert.Equal("abcdef1234567890:process_request+0x50", canonical);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_ShouldParseCanonicalString()
{
// Arrange
@@ -274,7 +287,8 @@ public sealed class CanonicalSymbolTests
Assert.Equal(0x100UL, symbol.Offset);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void Parse_ShouldReturnNullForInvalidFormat()
{
Assert.Null(CanonicalSymbol.Parse(""));
@@ -283,7 +297,8 @@ public sealed class CanonicalSymbolTests
Assert.Null(CanonicalSymbol.Parse("build:func+invalid"));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void RoundTrip_ShouldPreserveData()
{
// Arrange

View File

@@ -24,5 +24,6 @@
<ItemGroup>
<ProjectReference Include="../../StellaOps.Signals/StellaOps.Signals.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,11 +1,13 @@
using StellaOps.Signals.Lattice;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class UncertaintyTierTests
{
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(UncertaintyTier.T1, 0.50)]
[InlineData(UncertaintyTier.T2, 0.25)]
[InlineData(UncertaintyTier.T3, 0.10)]
@@ -15,7 +17,8 @@ public class UncertaintyTierTests
Assert.Equal(expected, tier.GetRiskModifier());
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(UncertaintyTier.T1, true)]
[InlineData(UncertaintyTier.T2, false)]
[InlineData(UncertaintyTier.T3, false)]
@@ -25,7 +28,8 @@ public class UncertaintyTierTests
Assert.Equal(expected, tier.BlocksNotAffected());
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(UncertaintyTier.T1, true)]
[InlineData(UncertaintyTier.T2, true)]
[InlineData(UncertaintyTier.T3, false)]
@@ -35,7 +39,8 @@ public class UncertaintyTierTests
Assert.Equal(expected, tier.RequiresWarning());
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(UncertaintyTier.T1, "High")]
[InlineData(UncertaintyTier.T2, "Medium")]
[InlineData(UncertaintyTier.T3, "Low")]
@@ -49,7 +54,8 @@ public class UncertaintyTierTests
public class UncertaintyTierCalculatorTests
{
// U1 (MissingSymbolResolution) tier calculation
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("U1", 0.7, UncertaintyTier.T1)]
[InlineData("U1", 0.8, UncertaintyTier.T1)]
[InlineData("U1", 0.4, UncertaintyTier.T2)]
@@ -62,7 +68,8 @@ public class UncertaintyTierCalculatorTests
}
// U2 (MissingPurl) tier calculation
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("U2", 0.5, UncertaintyTier.T2)]
[InlineData("U2", 0.6, UncertaintyTier.T2)]
[InlineData("U2", 0.4, UncertaintyTier.T3)]
@@ -73,7 +80,8 @@ public class UncertaintyTierCalculatorTests
}
// U3 (UntrustedAdvisory) tier calculation
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("U3", 0.6, UncertaintyTier.T3)]
[InlineData("U3", 0.8, UncertaintyTier.T3)]
[InlineData("U3", 0.5, UncertaintyTier.T4)]
@@ -84,7 +92,8 @@ public class UncertaintyTierCalculatorTests
}
// U4 (Unknown) always T1
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("U4", 0.0, UncertaintyTier.T1)]
[InlineData("U4", 0.5, UncertaintyTier.T1)]
[InlineData("U4", 1.0, UncertaintyTier.T1)]
@@ -94,7 +103,8 @@ public class UncertaintyTierCalculatorTests
}
// Unknown code defaults to T4
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData("Unknown", 0.5, UncertaintyTier.T4)]
[InlineData("", 0.5, UncertaintyTier.T4)]
public void CalculateTier_UnknownCode_ReturnsT4(string code, double entropy, UncertaintyTier expected)
@@ -102,14 +112,16 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(expected, UncertaintyTierCalculator.CalculateTier(code, entropy));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CalculateAggregateTier_WithEmptySequence_ReturnsT4()
{
var result = UncertaintyTierCalculator.CalculateAggregateTier(Array.Empty<(string, double)>());
Assert.Equal(UncertaintyTier.T4, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CalculateAggregateTier_ReturnsMaxSeverity()
{
var states = new[] { ("U1", 0.3), ("U2", 0.6), ("U3", 0.5) }; // T3, T2, T4
@@ -117,7 +129,8 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(UncertaintyTier.T2, result); // Maximum severity (lowest enum value)
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CalculateAggregateTier_StopsAtT1()
{
var states = new[] { ("U4", 1.0), ("U1", 0.3) }; // T1, T3
@@ -125,7 +138,8 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(UncertaintyTier.T1, result);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0.5, UncertaintyTier.T4, 0.1, 0.5, 0.525)] // No tier modifier for T4, but entropy boost applies
[InlineData(0.5, UncertaintyTier.T3, 0.1, 0.5, 0.575)] // +10% + entropy boost
[InlineData(0.5, UncertaintyTier.T2, 0.1, 0.5, 0.65)] // +25% + entropy boost
@@ -142,7 +156,8 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(expected, result, 3);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CalculateRiskScore_ClampsToCeiling()
{
var result = UncertaintyTierCalculator.CalculateRiskScore(
@@ -150,7 +165,8 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(1.0, result);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public void CreateUnknownState_ReturnsU4WithMaxEntropy()
{
var (code, name, entropy) = UncertaintyTierCalculator.CreateUnknownState();
@@ -159,7 +175,8 @@ public class UncertaintyTierCalculatorTests
Assert.Equal(1.0, entropy);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(10, 100, 0.1)]
[InlineData(50, 100, 0.5)]
[InlineData(0, 100, 0.0)]

View File

@@ -11,6 +11,8 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class UnknownsDecayServiceTests
@@ -55,7 +57,8 @@ public class UnknownsDecayServiceTests
#region ApplyDecayAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayAsync_EmptySubject_ReturnsZeroCounts()
{
var (decayService, _) = CreateServices();
@@ -70,7 +73,8 @@ public class UnknownsDecayServiceTests
Assert.Equal(0, result.BandChanges);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayAsync_SingleUnknown_UpdatesAndPersists()
{
var (decayService, _) = CreateServices();
@@ -100,7 +104,8 @@ public class UnknownsDecayServiceTests
Assert.True(updated[0].UpdatedAt >= now);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayAsync_BandChangesTracked()
{
var (decayService, _) = CreateServices();
@@ -136,7 +141,8 @@ public class UnknownsDecayServiceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayAsync_MultipleUnknowns_ProcessesAll()
{
var (decayService, _) = CreateServices();
@@ -186,7 +192,8 @@ public class UnknownsDecayServiceTests
#region RunNightlyDecayBatchAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunNightlyDecayBatchAsync_ProcessesAllSubjects()
{
var (decayService, _) = CreateServices();
@@ -224,7 +231,8 @@ public class UnknownsDecayServiceTests
Assert.True(result.Duration >= TimeSpan.Zero);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunNightlyDecayBatchAsync_RespectsMaxSubjectsLimit()
{
var decayOptions = new UnknownsDecayOptions { MaxSubjectsPerBatch = 1 };
@@ -278,7 +286,8 @@ public class UnknownsDecayServiceTests
Assert.Equal(1, result.TotalUnknowns);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunNightlyDecayBatchAsync_CancellationRespected()
{
var (decayService, _) = CreateServices();
@@ -311,7 +320,8 @@ public class UnknownsDecayServiceTests
#region ApplyDecayToUnknownAsync Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayToUnknownAsync_UpdatesScoringFields()
{
var (decayService, _) = CreateServices();
@@ -342,7 +352,8 @@ public class UnknownsDecayServiceTests
Assert.NotNull(result.NormalizationTrace);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayToUnknownAsync_SetsNextRescanBasedOnBand()
{
var (decayService, _) = CreateServices();
@@ -368,7 +379,8 @@ public class UnknownsDecayServiceTests
#region Decay Result Aggregation Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ApplyDecayAsync_ResultCountsAreAccurate()
{
var (decayService, _) = CreateServices();

View File

@@ -7,11 +7,13 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class UnknownsIngestionServiceTests
{
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_StoresNormalizedUnknowns()
{
var repo = new InMemoryUnknownsRepository();
@@ -42,7 +44,8 @@ public class UnknownsIngestionServiceTests
Assert.Equal("pkg:pypi/foo", repo.Stored[0].Purl);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task IngestAsync_ThrowsWhenEmpty()
{
var repo = new InMemoryUnknownsRepository();

View File

@@ -12,6 +12,7 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
/// <summary>
@@ -48,7 +49,8 @@ public sealed class UnknownsScoringIntegrationTests
#region End-to-End Flow Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EndToEnd_IngestScoreAndQueryByBand()
{
// Arrange: Create unknowns with varying factors
@@ -140,7 +142,8 @@ public sealed class UnknownsScoringIntegrationTests
hotUnknown.NormalizationTrace.Should().NotBeNull();
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EndToEnd_RecomputePreservesExistingData()
{
// Arrange
@@ -178,7 +181,8 @@ public sealed class UnknownsScoringIntegrationTests
retrieved.SubjectKey.Should().Be(subjectKey);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task EndToEnd_MultipleSubjectsIndependent()
{
// Arrange: Create unknowns in two different subjects
@@ -235,7 +239,8 @@ public sealed class UnknownsScoringIntegrationTests
#region Rescan Scheduling Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Rescan_GetDueForRescan_ReturnsCorrectBandItems()
{
// Arrange: Create unknowns with different bands
@@ -281,7 +286,8 @@ public sealed class UnknownsScoringIntegrationTests
warmDue.Should().NotContain(u => u.Id == "warm-rescan", "WARM item not yet due");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Rescan_NextScheduledRescan_SetByBand()
{
// Arrange
@@ -338,7 +344,8 @@ public sealed class UnknownsScoringIntegrationTests
#region Query and Pagination Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Query_PaginationWorks()
{
// Arrange
@@ -367,7 +374,8 @@ public sealed class UnknownsScoringIntegrationTests
page1.Select(u => u.Id).Should().NotIntersectWith(page2.Select(u => u.Id));
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Query_FilterByBandReturnsOnlyMatchingItems()
{
// Arrange
@@ -402,7 +410,8 @@ public sealed class UnknownsScoringIntegrationTests
#region Explain / Normalization Trace Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Explain_NormalizationTraceContainsAllFactors()
{
// Arrange
@@ -474,7 +483,8 @@ public sealed class UnknownsScoringIntegrationTests
trace.AssignedBand.Should().Be(explained.Band.ToString());
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Explain_TraceEnablesReplay()
{
// Arrange: Score an unknown
@@ -521,7 +531,8 @@ public sealed class UnknownsScoringIntegrationTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_SameInputsProduceSameScores()
{
// Arrange
@@ -575,7 +586,8 @@ public sealed class UnknownsScoringIntegrationTests
scored1.StalenessScore.Should().Be(scored2.StalenessScore);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task Determinism_ConsecutiveRecomputesProduceSameResults()
{
// Arrange

View File

@@ -11,6 +11,7 @@ using StellaOps.Signals.Persistence;
using StellaOps.Signals.Services;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Signals.Tests;
public class UnknownsScoringServiceTests
@@ -43,7 +44,8 @@ public class UnknownsScoringServiceTests
#region Staleness Exponential Decay Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_ExponentialDecay_FreshEvidence_LowStaleness()
{
// Fresh evidence (analyzed today) should have low staleness
@@ -66,7 +68,8 @@ public class UnknownsScoringServiceTests
Assert.Equal(0, scored.DaysSinceLastAnalysis);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_ExponentialDecay_StaleEvidence_HighStaleness()
{
// Old evidence (14 days) should have high staleness
@@ -89,7 +92,8 @@ public class UnknownsScoringServiceTests
Assert.Equal(14, scored.DaysSinceLastAnalysis);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_ExponentialDecay_NeverAnalyzed_MaxStaleness()
{
// Never analyzed should have maximum staleness
@@ -112,7 +116,8 @@ public class UnknownsScoringServiceTests
Assert.Equal(_defaultOptions.StalenessMaxDays, scored.DaysSinceLastAnalysis);
}
[Theory]
[Trait("Category", TestCategories.Unit)]
[Theory]
[InlineData(0, 0.0)] // Fresh
[InlineData(7, 0.35)] // Half tau - moderate staleness
[InlineData(14, 0.70)] // At tau - significant staleness
@@ -143,7 +148,8 @@ public class UnknownsScoringServiceTests
#region Band Assignment Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_BandAssignment_HotThreshold()
{
// High score should assign HOT band
@@ -178,7 +184,8 @@ public class UnknownsScoringServiceTests
$"Expected score >= {_defaultOptions.HotThreshold} for HOT, got {scored.Score}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_BandAssignment_WarmThreshold()
{
var service = CreateService();
@@ -211,7 +218,8 @@ public class UnknownsScoringServiceTests
$"Expected score < {_defaultOptions.HotThreshold} for WARM, got {scored.Score}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_BandAssignment_ColdThreshold()
{
var service = CreateService();
@@ -238,7 +246,8 @@ public class UnknownsScoringServiceTests
$"Expected score < {_defaultOptions.WarmThreshold} for COLD, got {scored.Score}");
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_BandAssignment_CustomThresholds()
{
var customOptions = new UnknownsScoringOptions
@@ -278,7 +287,8 @@ public class UnknownsScoringServiceTests
#region Weight Formula Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_WeightedFormula_VerifyComponents()
{
var service = CreateService();
@@ -320,7 +330,8 @@ public class UnknownsScoringServiceTests
Assert.InRange(scored.Score, 0.0, 1.0);
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_WeightedFormula_WeightsSumToOne()
{
// Verify default weights sum to 1.0
@@ -337,7 +348,8 @@ public class UnknownsScoringServiceTests
#region Rescan Scheduling Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_RescanScheduling_HotBand()
{
var service = CreateService();
@@ -369,7 +381,8 @@ public class UnknownsScoringServiceTests
}
}
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_RescanScheduling_ColdBand()
{
var service = CreateService();
@@ -395,7 +408,8 @@ public class UnknownsScoringServiceTests
#region Determinism Tests
[Fact]
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task ScoreUnknown_Determinism_SameInputsSameOutput()
{
var service = CreateService();