using System.Text.Json; using StellaOps.Scanner.Analyzers.Lang; using StellaOps.Scanner.Analyzers.Lang.Ruby; using StellaOps.Scanner.Analyzers.Lang.Tests.Harness; using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities; using StellaOps.Scanner.Core.Contracts; namespace StellaOps.Scanner.Analyzers.Lang.Ruby.Tests; public sealed class RubyLanguageAnalyzerTests { [Fact] public async Task SimpleWorkspaceProducesDeterministicOutputAsync() { var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app"); var goldenPath = Path.Combine(fixturePath, "expected.json"); var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() }; await LanguageAnalyzerTestHarness.AssertDeterministicAsync( fixturePath, goldenPath, analyzers, TestContext.Current.CancellationToken); } [Fact] public async Task AnalyzerEmitsObservationPayloadWithSummaryAsync() { var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app"); var store = new ScanAnalysisStore(); var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() }; var engine = new LanguageAnalyzerEngine(analyzers); var context = new LanguageAnalyzerContext( fixturePath, TimeProvider.System, usageHints: null, services: null, analysisStore: store); var result = await engine.AnalyzeAsync(context, TestContext.Current.CancellationToken); var snapshots = result.ToSnapshots(); var summary = Assert.Single(snapshots, snapshot => snapshot.Type == "ruby-observation"); Assert.Equal("Ruby Observation Summary", summary.Name); Assert.Equal("observation::ruby", summary.ComponentKey); Assert.True(summary.Metadata.TryGetValue("ruby.observation.packages", out var packageCount)); Assert.Equal("11", packageCount); Assert.Equal("3", summary.Metadata["ruby.observation.runtime_edges"]); Assert.Equal("true", summary.Metadata["ruby.observation.capability.exec"]); Assert.Equal("true", summary.Metadata["ruby.observation.capability.net"]); Assert.Equal("true", summary.Metadata["ruby.observation.capability.serialization"]); Assert.Equal("2.4.22", summary.Metadata["ruby.observation.bundler_version"]); Assert.True(store.TryGet(ScanAnalysisKeys.RubyObservationPayload, out AnalyzerObservationPayload payload)); Assert.Equal("ruby", payload.AnalyzerId); Assert.Equal("ruby.observation", payload.Kind); Assert.Equal("application/json", payload.MediaType); Assert.NotNull(payload.Metadata); Assert.Equal("11", payload.Metadata!["ruby.observation.packages"]); using var document = JsonDocument.Parse(payload.Content.ToArray()); var root = document.RootElement; var packages = root.GetProperty("packages"); Assert.Equal(11, packages.GetArrayLength()); var runtimeEdges = root.GetProperty("runtimeEdges"); Assert.True(runtimeEdges.GetArrayLength() >= 1); var capabilities = root.GetProperty("capabilities"); Assert.True(capabilities.GetProperty("usesExec").GetBoolean()); Assert.True(capabilities.GetProperty("usesNetwork").GetBoolean()); Assert.True(capabilities.GetProperty("usesSerialization").GetBoolean()); Assert.Equal("2.4.22", root.GetProperty("bundledWith").GetString()); } [Fact] public async Task ComplexWorkspaceProducesDeterministicOutputAsync() { var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "complex-app"); var goldenPath = Path.Combine(fixturePath, "expected.json"); var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() }; await LanguageAnalyzerTestHarness.AssertDeterministicAsync( fixturePath, goldenPath, analyzers, TestContext.Current.CancellationToken); } }