consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -376,9 +376,9 @@ public sealed class AnalyticsIngestionEdgeCaseTests
[Fact]
public void ResolveArtifactVersion_HandlesDigestInTag()
{
var envelope = new OrchestratorEventEnvelope
var envelope = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com/repo@sha256:abc123"
}
@@ -393,9 +393,9 @@ public sealed class AnalyticsIngestionEdgeCaseTests
[Fact]
public void ResolveArtifactVersion_HandlesPortInRegistry()
{
var envelope = new OrchestratorEventEnvelope
var envelope = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com:5000/repo:v1.2.3"
}
@@ -409,9 +409,9 @@ public sealed class AnalyticsIngestionEdgeCaseTests
[Fact]
public void ResolveArtifactVersion_ReturnsNullForPortOnly()
{
var envelope = new OrchestratorEventEnvelope
var envelope = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com:5000/repo"
}

View File

@@ -39,9 +39,9 @@ public sealed class AnalyticsIngestionHelpersTests
[Fact]
public void ResolveArtifactVersion_ParsesImageTag()
{
var envelope = new OrchestratorEventEnvelope
var envelope = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com/repo:1.2.3"
}
@@ -53,9 +53,9 @@ public sealed class AnalyticsIngestionHelpersTests
[Fact]
public void ResolveArtifactVersion_ReturnsNullWhenMissingTag()
{
var envelope = new OrchestratorEventEnvelope
var envelope = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com/repo"
}
@@ -89,26 +89,26 @@ public sealed class AnalyticsIngestionHelpersTests
[Fact]
public void ResolveArtifactName_PrefersRepoThenImageThenComponent()
{
var withRepo = new OrchestratorEventEnvelope
var withRepo = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Repo = "github.com/stellaops/core",
Image = "registry.example.com/stellaops/core:1.2.3",
Component = "stellaops-core"
}
};
var withImage = new OrchestratorEventEnvelope
var withImage = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Image = "registry.example.com/stellaops/console:2.0.0",
Component = "stellaops-console"
}
};
var withComponent = new OrchestratorEventEnvelope
var withComponent = new JobEngineEventEnvelope
{
Scope = new OrchestratorEventScope
Scope = new JobEngineEventScope
{
Component = "stellaops-agent"
}
@@ -117,7 +117,7 @@ public sealed class AnalyticsIngestionHelpersTests
Assert.Equal("github.com/stellaops/core", AnalyticsIngestionService.ResolveArtifactName(withRepo));
Assert.Equal("registry.example.com/stellaops/console:2.0.0", AnalyticsIngestionService.ResolveArtifactName(withImage));
Assert.Equal("stellaops-agent", AnalyticsIngestionService.ResolveArtifactName(withComponent));
Assert.Equal("unknown", AnalyticsIngestionService.ResolveArtifactName(new OrchestratorEventEnvelope()));
Assert.Equal("unknown", AnalyticsIngestionService.ResolveArtifactName(new JobEngineEventEnvelope()));
}
[Fact]

View File

@@ -19,8 +19,8 @@ public sealed class ScannerPlatformEventsBehaviorTests
[Fact]
public void IsSupportedScannerEventKind_RecognizesReportReadyAndScanCompleted()
{
Assert.True(AnalyticsIngestionService.IsSupportedScannerEventKind(OrchestratorEventKinds.ScannerReportReady));
Assert.True(AnalyticsIngestionService.IsSupportedScannerEventKind(OrchestratorEventKinds.ScannerScanCompleted));
Assert.True(AnalyticsIngestionService.IsSupportedScannerEventKind(JobEngineEventKinds.ScannerReportReady));
Assert.True(AnalyticsIngestionService.IsSupportedScannerEventKind(JobEngineEventKinds.ScannerScanCompleted));
Assert.False(AnalyticsIngestionService.IsSupportedScannerEventKind("scanner.unknown"));
}
@@ -55,7 +55,7 @@ public sealed class ScannerPlatformEventsBehaviorTests
var result = AnalyticsIngestionService.TryDeserializeScannerPayload(
dsseElement,
OrchestratorEventKinds.ScannerReportReady,
JobEngineEventKinds.ScannerReportReady,
SerializerOptions,
out var parsed);
@@ -80,7 +80,7 @@ public sealed class ScannerPlatformEventsBehaviorTests
var result = AnalyticsIngestionService.TryDeserializeScannerPayload(
completedElement,
OrchestratorEventKinds.ScannerScanCompleted,
JobEngineEventKinds.ScannerScanCompleted,
SerializerOptions,
out var parsed);

View File

@@ -12,6 +12,8 @@ using StellaOps.Signals.EvidenceWeightedScore;
using StellaOps.Signals.UnifiedScore;
using StellaOps.Signals.UnifiedScore.Replay;
using StellaOps.TestKit;
using System.Text;
using System.Text.Json;
using Xunit;
namespace StellaOps.Platform.WebService.Tests;
@@ -179,6 +181,26 @@ public sealed class ScoreEndpointsTests
Assert.True(result.Value.ComputedAt > DateTimeOffset.UtcNow.AddMinutes(-1));
}
[Fact]
public async Task EvaluateAsync_WithPartialSignals_ReturnsUnknownsAndProofReference()
{
var request = new ScoreEvaluateRequest
{
Signals = new SignalInputs
{
Reachability = 0.8
}
};
var result = await _service.EvaluateAsync(_context, request);
Assert.NotNull(result.Value.Unknowns);
Assert.Contains("runtime", result.Value.Unknowns!);
Assert.Contains("backport", result.Value.Unknowns!);
Assert.NotNull(result.Value.ProofRef);
Assert.StartsWith("proof://score/", result.Value.ProofRef, StringComparison.Ordinal);
}
[Fact]
public async Task EvaluateAsync_DifferentTenants_ProduceDifferentScoreIds()
{
@@ -238,6 +260,49 @@ public sealed class ScoreEndpointsTests
Assert.Null(result.Value);
}
[Fact]
public async Task GetExplanationAsync_NonExistent_ReturnsNull()
{
var result = await _service.GetExplanationAsync(_context, "sha256:missing");
Assert.Null(result.Value);
}
[Fact]
public async Task GetExplanationAsync_WithPersistedDigest_ReturnsCanonicalContract()
{
var record = new ScoreHistoryRecord
{
Id = "score_123",
TenantId = "test-tenant",
ProjectId = "proj-a",
CveId = "CVE-2026-0001",
Purl = "pkg:generic/demo@1.0.0",
Score = 0.62m,
Band = "Investigate",
WeightsVersion = "v-test",
SignalSnapshot = "{\"vex\":{\"isPresent\":true},\"epss\":{\"isPresent\":true},\"reachability\":{\"isPresent\":false},\"runtime\":{\"isPresent\":true},\"backport\":{\"isPresent\":false},\"sbom\":{\"isPresent\":true}}",
ReplayDigest = "sha256:abc123",
CreatedAt = DateTimeOffset.Parse("2026-02-26T12:00:00Z")
};
_scoreHistoryStore
.GetByReplayDigestAsync("sha256:abc123", "test-tenant", Arg.Any<CancellationToken>())
.Returns(record);
var result = await _service.GetExplanationAsync(_context, "SHA256:ABC123");
Assert.NotNull(result.Value);
Assert.Equal("score.explain.v1", result.Value.ContractVersion);
Assert.Equal("sha256:abc123", result.Value.Digest);
Assert.Equal("score_123", result.Value.ScoreId);
Assert.Equal(62, result.Value.FinalScore);
Assert.Equal("sha256:abc123", result.Value.DeterministicInputHash);
Assert.Equal("/api/v1/score/score_123/replay", result.Value.ReplayLink);
Assert.Equal(6, result.Value.Factors.Count);
Assert.Equal(2, result.Value.Sources.Count);
}
#endregion
#region TSF-005: ListWeightManifestsAsync
@@ -348,7 +413,7 @@ public sealed class ScoreEndpointsTests
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = "eyJwYXlsb2FkIjoiZXlKMFpYTjBJam9pYUdWc2JHOGlmUT09In0=",
SignedReplayLogDsse = CreateReplayEnvelope(),
OriginalInputs = new ScoreVerifyInputs
{
Signals = new SignalInputs
@@ -378,7 +443,7 @@ public sealed class ScoreEndpointsTests
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = "eyJwYXlsb2FkIjoiZXlKMFpYTjBJam9pYUdWc2JHOGlmUT09In0=",
SignedReplayLogDsse = CreateReplayEnvelope(),
OriginalInputs = null
};
@@ -393,7 +458,7 @@ public sealed class ScoreEndpointsTests
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = "eyJwYXlsb2FkIjoiZXlKMFpYTjBJam9pYUdWc2JHOGlmUT09In0=",
SignedReplayLogDsse = CreateReplayEnvelope(),
OriginalInputs = new ScoreVerifyInputs
{
Signals = new SignalInputs { Reachability = 0.5 }
@@ -411,7 +476,7 @@ public sealed class ScoreEndpointsTests
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = "eyJwYXlsb2FkIjoiZXlKMFpYTjBJam9pYUdWc2JHOGlmUT09In0=",
SignedReplayLogDsse = CreateReplayEnvelope(),
OriginalInputs = new ScoreVerifyInputs
{
Signals = new SignalInputs { Reachability = 0.5 },
@@ -425,6 +490,48 @@ public sealed class ScoreEndpointsTests
Assert.True(result.Value.Verified);
}
[Fact]
public async Task VerifyReplayAsync_WithMismatchedReplayPayload_ReturnsDeterministicDifferences()
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = CreateReplayEnvelope(finalScore: 99, ewsDigest: "sha256:does-not-match"),
OriginalInputs = new ScoreVerifyInputs
{
Signals = new SignalInputs { Reachability = 0.5, Runtime = 0.5 }
}
};
var result = await _service.VerifyReplayAsync(_context, request);
Assert.False(result.Value.Verified);
Assert.False(result.Value.ScoreMatches);
Assert.False(result.Value.DigestMatches);
Assert.NotNull(result.Value.Differences);
Assert.Contains(result.Value.Differences!, diff => diff.Field == "final_score");
Assert.Contains(result.Value.Differences!, diff => diff.Field == "ews_digest");
}
[Fact]
public async Task VerifyReplayAsync_WithMalformedEnvelope_ReturnsDeterministicEnvelopeError()
{
var request = new ScoreVerifyRequest
{
SignedReplayLogDsse = "not-base64",
OriginalInputs = new ScoreVerifyInputs
{
Signals = new SignalInputs { Reachability = 0.5, Runtime = 0.5 }
}
};
var result = await _service.VerifyReplayAsync(_context, request);
Assert.False(result.Value.Verified);
Assert.NotNull(result.Value.Differences);
var envelopeDifference = Assert.Single(result.Value.Differences!, diff => diff.Field == "signed_replay_log_dsse");
Assert.Equal("valid_dsse_envelope", envelopeDifference.Expected);
}
#endregion
#region TSF-011: GetReplayAsync
@@ -605,5 +712,28 @@ public sealed class ScoreEndpointsTests
.Returns(result);
}
private static string CreateReplayEnvelope(int? finalScore = null, string? ewsDigest = null)
{
var payload = new Dictionary<string, object?>();
if (finalScore.HasValue)
{
payload["final_score"] = finalScore.Value;
}
if (!string.IsNullOrWhiteSpace(ewsDigest))
{
payload["ews_digest"] = ewsDigest;
}
var payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload));
var envelope = new Dictionary<string, object?>
{
["payloadType"] = "application/vnd.stellaops.score-replay+json",
["payload"] = Convert.ToBase64String(payloadBytes)
};
return Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(envelope)));
}
#endregion
}