up the blokcing tasks
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Risk Bundle CI / risk-bundle-build (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Risk Bundle CI / risk-bundle-offline-kit (push) Has been cancelled
Risk Bundle CI / publish-checksums (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-11 02:32:18 +02:00
parent 92bc4d3a07
commit 49922dff5a
474 changed files with 76071 additions and 12411 deletions

View File

@@ -0,0 +1,379 @@
using System.Text;
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Runtime;
namespace StellaOps.Scanner.Analyzers.Lang.Java.Tests;
/// <summary>
/// Tests for SCANNER-ANALYZERS-JAVA-21-010: Runtime ingestion via Java agent + JFR reader.
/// </summary>
public sealed class JavaRuntimeIngestionTests
{
[Fact]
public async Task ParseAsync_ClassLoadEvent_ProducesRuntimeClassEdge()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/MyService","class_loader":"app","source":"/app/lib/myservice.jar","initiating_class":"com/example/Main"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.Events);
Assert.Single(result.RuntimeEdges);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeClass, edge.EdgeType);
Assert.Equal("com/example/Main", edge.SourceClass);
Assert.Equal("com/example/MyService", edge.TargetClass);
Assert.Equal(JavaRuntimeEdgeReason.ClassLoadApplication, edge.Reason);
Assert.Equal(1.0, edge.Confidence);
}
[Fact]
public async Task ParseAsync_ServiceLoaderEvent_ProducesRuntimeSpiEdge()
{
var ndjson = """
{"type":"java.service.load","ts":"2025-12-10T10:00:01.000Z","service_interface":"com/example/spi/Service","providers":[{"provider_class":"com/example/impl/ServiceImpl","source":"/app/lib/impl.jar"}],"initiating_class":"com/example/ServiceLoader"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEdges);
Assert.Single(result.RuntimeEntrypoints);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeSpi, edge.EdgeType);
Assert.Equal("com/example/spi/Service", edge.SourceClass);
Assert.Equal("com/example/impl/ServiceImpl", edge.TargetClass);
var entrypoint = result.RuntimeEntrypoints[0];
Assert.Equal(JavaRuntimeEntrypointType.ServiceProvider, entrypoint.EntrypointType);
Assert.Equal("com/example/impl/ServiceImpl", entrypoint.ClassName);
}
[Fact]
public async Task ParseAsync_NativeLoadEvent_ProducesRuntimeNativeLoadEdge()
{
var ndjson = """
{"type":"java.native.load","ts":"2025-12-10T10:00:02.000Z","library_name":"jni_native","resolved_path":"/usr/lib/libjni_native.so","load_method":"System.loadLibrary","initiating_class":"com/example/NativeLoader","success":true}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEdges);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeNativeLoad, edge.EdgeType);
Assert.Equal("com/example/NativeLoader", edge.SourceClass);
Assert.Equal("jni_native", edge.TargetClass);
Assert.Equal(JavaRuntimeEdgeReason.SystemLoadLibrary, edge.Reason);
Assert.Equal(1.0, edge.Confidence);
}
[Fact]
public async Task ParseAsync_FailedNativeLoad_ProducesLowerConfidenceEdge()
{
var ndjson = """
{"type":"java.native.load","ts":"2025-12-10T10:00:02.000Z","library_name":"missing_lib","load_method":"System.loadLibrary","initiating_class":"com/example/NativeLoader","success":false}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEdges);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeReason.NativeLoadFailure, edge.Reason);
Assert.Equal(0.5, edge.Confidence);
}
[Fact]
public async Task ParseAsync_ReflectionEvent_ProducesRuntimeReflectionEdge()
{
var ndjson = """
{"type":"java.reflection.access","ts":"2025-12-10T10:00:03.000Z","target_class":"com/example/DynamicClass","reflection_method":"Class.forName","initiating_class":"com/example/Reflector"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEdges);
Assert.Single(result.RuntimeEntrypoints);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeReflection, edge.EdgeType);
Assert.Equal(JavaRuntimeEdgeReason.ClassForName, edge.Reason);
Assert.Equal(0.9, edge.Confidence);
var entrypoint = result.RuntimeEntrypoints[0];
Assert.Equal(JavaRuntimeEntrypointType.ReflectionTarget, entrypoint.EntrypointType);
}
[Fact]
public async Task ParseAsync_ResourceAccessEvent_ProducesEdgeOnlyWhenFound()
{
var ndjson = """
{"type":"java.resource.access","ts":"2025-12-10T10:00:04.000Z","resource_name":"config.properties","source":"/app/conf.jar","initiating_class":"com/example/ConfigLoader","found":true}
{"type":"java.resource.access","ts":"2025-12-10T10:00:05.000Z","resource_name":"missing.properties","initiating_class":"com/example/ConfigLoader","found":false}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Equal(2, result.Events.Length);
Assert.Single(result.RuntimeEdges); // Only found=true produces edge
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeResource, edge.EdgeType);
Assert.Equal("config.properties", edge.TargetClass);
}
[Fact]
public async Task ParseAsync_ModuleResolveEvent_ProducesRuntimeModuleEdge()
{
var ndjson = """
{"type":"java.module.resolve","ts":"2025-12-10T10:00:06.000Z","module_name":"com.example.api","module_location":"file:///app/lib/api.jar","required_by":"com.example.app","is_open":false}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEdges);
var edge = result.RuntimeEdges[0];
Assert.Equal(JavaRuntimeEdgeType.RuntimeModule, edge.EdgeType);
Assert.Equal("com.example.app", edge.SourceClass);
Assert.Equal("com.example.api", edge.TargetClass);
Assert.Equal(JavaRuntimeEdgeReason.ModuleRequires, edge.Reason);
}
[Fact]
public async Task ParseAsync_DeduplicatesEdgesByDefault()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/MyService","class_loader":"app","initiating_class":"com/example/Main"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/MyService","class_loader":"app","initiating_class":"com/example/Main"}
{"type":"java.class.load","ts":"2025-12-10T10:00:02.000Z","class_name":"com/example/MyService","class_loader":"app","initiating_class":"com/example/Main"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Equal(3, result.Events.Length);
Assert.Single(result.RuntimeEdges); // Deduplicated
}
[Fact]
public async Task ParseAsync_WithDeduplicationDisabled_ProducesAllEdges()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/MyService","class_loader":"app","initiating_class":"com/example/Main"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/MyService","class_loader":"app","initiating_class":"com/example/Main"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var config = new JavaRuntimeIngestionConfig(DeduplicateEdges: false);
var result = await JavaRuntimeIngestor.IngestAsync(stream, config);
Assert.Equal(2, result.RuntimeEdges.Length); // Not deduplicated
}
[Fact]
public async Task ParseAsync_FiltersJdkClassesByDefault()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"java/lang/String","class_loader":"bootstrap","initiating_class":"com/example/Main"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/MyClass","class_loader":"app","initiating_class":"com/example/Main"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.Events);
Assert.Single(result.RuntimeEdges);
Assert.Equal("com/example/MyClass", result.RuntimeEdges[0].TargetClass);
}
[Fact]
public async Task ParseAsync_IncludesJdkClassesWhenConfigured()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"java/lang/String","class_loader":"bootstrap","initiating_class":"com/example/Main"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/MyClass","class_loader":"app","initiating_class":"com/example/Main"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var config = new JavaRuntimeIngestionConfig(IncludeJdkClasses: true);
var result = await JavaRuntimeIngestor.IngestAsync(stream, config);
Assert.Equal(2, result.Events.Length);
Assert.Equal(2, result.RuntimeEdges.Length);
}
[Fact]
public async Task ParseAsync_RespectsMaxEventsLimit()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/Class1","class_loader":"app"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/Class2","class_loader":"app"}
{"type":"java.class.load","ts":"2025-12-10T10:00:02.000Z","class_name":"com/example/Class3","class_loader":"app"}
{"type":"java.class.load","ts":"2025-12-10T10:00:03.000Z","class_name":"com/example/Class4","class_loader":"app"}
{"type":"java.class.load","ts":"2025-12-10T10:00:04.000Z","class_name":"com/example/Class5","class_loader":"app"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var config = new JavaRuntimeIngestionConfig(MaxEvents: 3);
var result = await JavaRuntimeIngestor.IngestAsync(stream, config);
Assert.Equal(3, result.Events.Length);
Assert.Single(result.Warnings);
Assert.Equal("MAX_EVENTS_REACHED", result.Warnings[0].WarningCode);
}
[Fact]
public async Task ParseAsync_ComputesContentHash()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/MyClass","class_loader":"app"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.NotEmpty(result.ContentHash);
Assert.Equal(64, result.ContentHash.Length); // SHA-256 hex is 64 chars
}
[Fact]
public async Task ParseAsync_SamContentProducesSameHash()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/MyClass","class_loader":"app"}
""";
using var stream1 = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
using var stream2 = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result1 = await JavaRuntimeIngestor.IngestAsync(stream1);
var result2 = await JavaRuntimeIngestor.IngestAsync(stream2);
Assert.Equal(result1.ContentHash, result2.ContentHash);
}
[Fact]
public async Task ParseAsync_PopulatesSummaryStatistics()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/Class1","class_loader":"app"}
{"type":"java.class.load","ts":"2025-12-10T10:00:01.000Z","class_name":"com/example/Class2","class_loader":"app"}
{"type":"java.service.load","ts":"2025-12-10T10:00:02.000Z","service_interface":"spi/Service","providers":[]}
{"type":"java.native.load","ts":"2025-12-10T10:00:03.000Z","library_name":"native","load_method":"System.loadLibrary","success":true}
{"type":"java.reflection.access","ts":"2025-12-10T10:00:04.000Z","target_class":"com/example/Dynamic","reflection_method":"Class.forName"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Equal(2, result.Summary.ClassLoadCount);
Assert.Equal(1, result.Summary.ServiceLoaderCount);
Assert.Equal(1, result.Summary.NativeLoadCount);
Assert.Equal(1, result.Summary.ReflectionCount);
Assert.Equal(DateTimeOffset.Parse("2025-12-10T10:00:00.000Z"), result.Summary.StartTime);
Assert.Equal(DateTimeOffset.Parse("2025-12-10T10:00:04.000Z"), result.Summary.EndTime);
}
[Fact]
public async Task ParseAsync_HandlesInvalidJson_ProducesWarning()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/Class1","class_loader":"app"}
{invalid json}
{"type":"java.class.load","ts":"2025-12-10T10:00:02.000Z","class_name":"com/example/Class2","class_loader":"app"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Equal(2, result.Events.Length); // Valid events parsed
Assert.Single(result.Warnings);
Assert.Equal("PARSE_ERROR", result.Warnings[0].WarningCode);
Assert.Equal(2, result.Warnings[0].Line);
}
[Fact]
public async Task ParseAsync_SkipsUnknownEventTypes()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/Class1","class_loader":"app"}
{"type":"java.unknown.event","ts":"2025-12-10T10:00:01.000Z","data":"something"}
{"type":"java.class.load","ts":"2025-12-10T10:00:02.000Z","class_name":"com/example/Class2","class_loader":"app"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Equal(2, result.Events.Length); // Only known event types
Assert.Empty(result.Warnings);
}
[Fact]
public async Task ParseAsync_HandlesEmptyStream()
{
using var stream = new MemoryStream(Array.Empty<byte>());
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Empty(result.Events);
Assert.Empty(result.RuntimeEdges);
Assert.Empty(result.Warnings);
}
[Fact]
public async Task ParseAsync_ComputesPathHash_WhenScrubPathsEnabled()
{
var ndjson = """
{"type":"java.class.load","ts":"2025-12-10T10:00:00.000Z","class_name":"com/example/Class1","class_loader":"app","source":"/app/lib/my.jar"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var config = new JavaRuntimeIngestionConfig(ScrubPaths: true);
var result = await JavaRuntimeIngestor.IngestAsync(stream, config);
var evt = result.Events[0] as JavaClassLoadEvent;
Assert.NotNull(evt);
Assert.NotNull(evt.SourceHash);
Assert.Equal(64, evt.SourceHash.Length);
}
[Fact]
public void ComputePathHash_NormalizesPathSeparators()
{
var windowsPath = "C:\\app\\lib\\my.jar";
var unixPath = "C:/app/lib/my.jar";
var hash1 = JavaRuntimeEventParser.ComputePathHash(windowsPath);
var hash2 = JavaRuntimeEventParser.ComputePathHash(unixPath);
Assert.Equal(hash1, hash2);
}
[Fact]
public async Task ParseAsync_TracksEntrypointInvocationCounts()
{
var ndjson = """
{"type":"java.reflection.access","ts":"2025-12-10T10:00:00.000Z","target_class":"com/example/Dynamic","reflection_method":"Class.forName","initiating_class":"com/example/A"}
{"type":"java.reflection.access","ts":"2025-12-10T10:00:01.000Z","target_class":"com/example/Dynamic","reflection_method":"Class.forName","initiating_class":"com/example/B"}
{"type":"java.reflection.access","ts":"2025-12-10T10:00:02.000Z","target_class":"com/example/Dynamic","reflection_method":"Class.forName","initiating_class":"com/example/C"}
""";
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(ndjson));
var result = await JavaRuntimeIngestor.IngestAsync(stream);
Assert.Single(result.RuntimeEntrypoints);
var entrypoint = result.RuntimeEntrypoints[0];
Assert.Equal("com/example/Dynamic", entrypoint.ClassName);
Assert.Equal(3, entrypoint.InvocationCount);
Assert.Equal(DateTimeOffset.Parse("2025-12-10T10:00:00.000Z"), entrypoint.FirstSeen);
}
}