using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Signals.Models; using StellaOps.Signals.Persistence; using StellaOps.Signals.Services; using Xunit; public class RuntimeFactsIngestionServiceTests { [Fact] public async Task IngestAsync_AggregatesHits_AndRecomputesReachability() { var factRepository = new InMemoryReachabilityFactRepository(); var scoringService = new RecordingScoringService(); var service = new RuntimeFactsIngestionService( factRepository, TimeProvider.System, scoringService, NullLogger.Instance); var request = new RuntimeFactsIngestRequest { Subject = new ReachabilitySubject { Component = "web", Version = "2.1.0" }, CallgraphId = "cg-123", Metadata = new Dictionary { { "source", "runtime" } }, Events = new List { new() { SymbolId = "svc.foo", HitCount = 2, Metadata = new Dictionary { { "pid", "12" } } }, new() { SymbolId = "svc.bar", HitCount = 1 }, new() { SymbolId = "svc.foo", HitCount = 3 } } }; var response = await service.IngestAsync(request, CancellationToken.None); Assert.Equal("web|2.1.0", response.SubjectKey); Assert.Equal("cg-123", response.CallgraphId); var persisted = factRepository.Last ?? throw new Xunit.Sdk.XunitException("Fact not persisted"); Assert.Equal(2, persisted.RuntimeFacts?.Count); var foo = persisted.RuntimeFacts?.Single(f => f.SymbolId == "svc.foo"); Assert.Equal(5, foo?.HitCount); var bar = persisted.RuntimeFacts?.Single(f => f.SymbolId == "svc.bar"); Assert.Equal(1, bar?.HitCount); var recorded = scoringService.LastRequest ?? throw new Xunit.Sdk.XunitException("Recompute not triggered"); Assert.Equal("cg-123", recorded.CallgraphId); Assert.Contains("svc.foo", recorded.Targets); Assert.Contains("svc.bar", recorded.RuntimeHits!); Assert.Equal("runtime", recorded.Metadata?["source"]); Assert.Equal("runtime", persisted.Metadata?["provenance.source"]); Assert.Equal("cg-123", persisted.Metadata?["provenance.callgraphId"]); Assert.NotNull(persisted.Metadata?["provenance.ingestedAt"]); } private sealed class InMemoryReachabilityFactRepository : IReachabilityFactRepository { public ReachabilityFactDocument? Last { get; private set; } public Task GetBySubjectAsync(string subjectKey, CancellationToken cancellationToken) { return Task.FromResult(Last); } public Task UpsertAsync(ReachabilityFactDocument document, CancellationToken cancellationToken) { Last = document; return Task.FromResult(document); } } private sealed class RecordingScoringService : IReachabilityScoringService { public ReachabilityRecomputeRequest? LastRequest { get; private set; } public Task RecomputeAsync(ReachabilityRecomputeRequest request, CancellationToken cancellationToken) { LastRequest = request; return Task.FromResult(new ReachabilityFactDocument { CallgraphId = request.CallgraphId, Subject = request.Subject, SubjectKey = request.Subject?.ToSubjectKey() ?? string.Empty, EntryPoints = request.EntryPoints, States = new List(), RuntimeFacts = new List() }); } } }