Add Ruby language analyzer and related functionality
- Introduced global usings for Ruby analyzer. - Implemented RubyLockData, RubyLockEntry, and RubyLockParser for handling Gemfile.lock files. - Created RubyPackage and RubyPackageCollector to manage Ruby packages and vendor cache. - Developed RubyAnalyzerPlugin and RubyLanguageAnalyzer for analyzing Ruby projects. - Added tests for Ruby language analyzer with sample Gemfile.lock and expected output. - Included necessary project files and references for the Ruby analyzer. - Added third-party licenses for tree-sitter dependencies.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.AdvisoryAI.Documents;
|
||||
using StellaOps.AdvisoryAI.Abstractions;
|
||||
using StellaOps.AdvisoryAI.Context;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests;
|
||||
|
||||
public sealed class AdvisoryPipelineOrchestratorTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreatePlanAsync_ComposesDeterministicPlan()
|
||||
{
|
||||
var structuredRetriever = new FakeStructuredRetriever();
|
||||
var vectorRetriever = new FakeVectorRetriever();
|
||||
var sbomRetriever = new FakeSbomContextRetriever();
|
||||
var options = Options.Create(new AdvisoryPipelineOptions());
|
||||
options.Value.Tasks[AdvisoryTaskType.Summary].VectorQueries.Clear();
|
||||
options.Value.Tasks[AdvisoryTaskType.Summary].VectorQueries.Add("summary-query");
|
||||
options.Value.Tasks[AdvisoryTaskType.Summary].VectorTopK = 2;
|
||||
options.Value.Tasks[AdvisoryTaskType.Summary].StructuredMaxChunks = 5;
|
||||
options.Value.Tasks[AdvisoryTaskType.Summary].PromptTemplate = "prompts/summary.liquid";
|
||||
var orchestrator = new AdvisoryPipelineOrchestrator(
|
||||
structuredRetriever,
|
||||
vectorRetriever,
|
||||
sbomRetriever,
|
||||
new DeterministicToolset(),
|
||||
options,
|
||||
NullLogger<AdvisoryPipelineOrchestrator>.Instance);
|
||||
|
||||
var request = new AdvisoryTaskRequest(
|
||||
AdvisoryTaskType.Summary,
|
||||
advisoryKey: "adv-key",
|
||||
artifactId: "artifact-1",
|
||||
artifactPurl: "pkg:docker/sample@1.0.0",
|
||||
policyVersion: "policy-42",
|
||||
profile: "default");
|
||||
|
||||
var plan = await orchestrator.CreatePlanAsync(request, CancellationToken.None);
|
||||
|
||||
Assert.Equal("prompts/summary.liquid", plan.PromptTemplate);
|
||||
Assert.Equal(2, plan.StructuredChunks.Length);
|
||||
Assert.Single(plan.VectorResults);
|
||||
Assert.Equal("summary-query", plan.VectorResults[0].Query);
|
||||
Assert.Equal(2, plan.VectorResults[0].Matches.Length);
|
||||
Assert.NotNull(plan.SbomContext);
|
||||
Assert.NotNull(plan.DependencyAnalysis);
|
||||
Assert.NotEmpty(plan.CacheKey);
|
||||
Assert.Equal("adv-key", plan.Metadata["advisory_key"]);
|
||||
Assert.Equal("Summary", plan.Metadata["task_type"]);
|
||||
Assert.Equal("1", plan.Metadata["runtime_path_count"]);
|
||||
|
||||
var secondPlan = await orchestrator.CreatePlanAsync(request, CancellationToken.None);
|
||||
Assert.Equal(plan.CacheKey, secondPlan.CacheKey);
|
||||
}
|
||||
|
||||
private sealed class FakeStructuredRetriever : IAdvisoryStructuredRetriever
|
||||
{
|
||||
public Task<AdvisoryRetrievalResult> RetrieveAsync(AdvisoryRetrievalRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var chunks = new[]
|
||||
{
|
||||
AdvisoryChunk.Create("doc-1", "doc-1:0001", "Summary", "summary[0]", "Summary section", new Dictionary<string, string>
|
||||
{
|
||||
["section"] = "Summary",
|
||||
}),
|
||||
AdvisoryChunk.Create("doc-1", "doc-1:0002", "Remediation", "remediation[0]", "Remediation section", new Dictionary<string, string>
|
||||
{
|
||||
["section"] = "Remediation",
|
||||
}),
|
||||
};
|
||||
|
||||
return Task.FromResult(AdvisoryRetrievalResult.Create(request.AdvisoryKey, chunks));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeVectorRetriever : IAdvisoryVectorRetriever
|
||||
{
|
||||
public Task<IReadOnlyList<VectorRetrievalMatch>> SearchAsync(VectorRetrievalRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var matches = new[]
|
||||
{
|
||||
new VectorRetrievalMatch("doc-1", "doc-1:0002", "Remediation section", 0.95, ImmutableDictionary<string, string>.Empty),
|
||||
new VectorRetrievalMatch("doc-1", "doc-1:0001", "Summary section", 0.90, ImmutableDictionary<string, string>.Empty),
|
||||
};
|
||||
|
||||
return Task.FromResult<IReadOnlyList<VectorRetrievalMatch>>(matches);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeSbomContextRetriever : ISbomContextRetriever
|
||||
{
|
||||
public Task<SbomContextResult> RetrieveAsync(SbomContextRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var versionTimeline = new[]
|
||||
{
|
||||
new SbomVersionTimelineEntry("1.0.0", DateTimeOffset.UtcNow.AddDays(-10), null, "affected", "scanner"),
|
||||
};
|
||||
|
||||
var dependencyPaths = new[]
|
||||
{
|
||||
new SbomDependencyPath(
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyNode("root", "1.0.0"),
|
||||
new SbomDependencyNode("runtime-lib", "2.1.0"),
|
||||
},
|
||||
isRuntime: true),
|
||||
new SbomDependencyPath(
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyNode("root", "1.0.0"),
|
||||
new SbomDependencyNode("dev-lib", "0.9.0"),
|
||||
},
|
||||
isRuntime: false),
|
||||
};
|
||||
|
||||
var result = SbomContextResult.Create(
|
||||
request.ArtifactId,
|
||||
request.Purl,
|
||||
versionTimeline,
|
||||
dependencyPaths);
|
||||
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using StellaOps.AdvisoryAI.Abstractions;
|
||||
using StellaOps.AdvisoryAI.Context;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests;
|
||||
|
||||
public sealed class AdvisoryPipelinePlanResponseTests
|
||||
{
|
||||
[Fact]
|
||||
public void FromPlan_ProjectsMetadataAndCounts()
|
||||
{
|
||||
var request = new AdvisoryTaskRequest(AdvisoryTaskType.Summary, "adv-key");
|
||||
var chunks = ImmutableArray.Create(
|
||||
AdvisoryChunk.Create("doc-1", "doc-1:0001", "Summary", "summary[0]", "Summary text", new Dictionary<string, string>
|
||||
{
|
||||
["section"] = "Summary",
|
||||
}),
|
||||
AdvisoryChunk.Create("doc-1", "doc-1:0002", "Remediation", "remediation[0]", "Remediation text", new Dictionary<string, string>
|
||||
{
|
||||
["section"] = "Remediation",
|
||||
}));
|
||||
|
||||
var vectorResults = ImmutableArray.Create(
|
||||
new AdvisoryVectorResult(
|
||||
"Summary query",
|
||||
ImmutableArray.Create(
|
||||
new VectorRetrievalMatch("doc-1", "doc-1:0001", "Summary text", 0.9, ImmutableDictionary<string, string>.Empty))));
|
||||
|
||||
var sbom = SbomContextResult.Create(
|
||||
"artifact-1",
|
||||
null,
|
||||
new[]
|
||||
{
|
||||
new SbomVersionTimelineEntry("1.0.0", DateTimeOffset.UtcNow.AddDays(-1), null, "affected", "scanner"),
|
||||
},
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyPath(
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyNode("root", "1.0.0"),
|
||||
},
|
||||
true),
|
||||
});
|
||||
|
||||
var dependency = DependencyAnalysisResult.Create(
|
||||
sbom.ArtifactId,
|
||||
sbom.DependencyPaths.Select(path => new DependencyNodeSummary(
|
||||
path.Nodes.Last().Identifier,
|
||||
Array.Empty<string>(),
|
||||
runtimeOccurrences: path.IsRuntime ? 1 : 0,
|
||||
developmentOccurrences: path.IsRuntime ? 0 : 1)),
|
||||
ImmutableDictionary<string, string>.Empty);
|
||||
|
||||
var plan = new AdvisoryTaskPlan(
|
||||
request,
|
||||
cacheKey: "ABC123",
|
||||
promptTemplate: "prompts/advisory/summary.liquid",
|
||||
structuredChunks: chunks,
|
||||
vectorResults: vectorResults,
|
||||
sbomContext: sbom,
|
||||
dependencyAnalysis: dependency,
|
||||
budget: new AdvisoryTaskBudget { PromptTokens = 1024, CompletionTokens = 256 },
|
||||
metadata: ImmutableDictionary<string, string>.Empty);
|
||||
|
||||
var response = AdvisoryPipelinePlanResponse.FromPlan(plan);
|
||||
|
||||
response.TaskType.Should().Be("Summary");
|
||||
response.CacheKey.Should().Be("ABC123");
|
||||
response.Chunks.Should().HaveCount(2);
|
||||
response.Vectors.Should().HaveCount(1);
|
||||
response.Sbom.Should().NotBeNull();
|
||||
response.Sbom!.DependencyNodeCount.Should().Be(1);
|
||||
response.Budget.CompletionTokens.Should().Be(256);
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,8 @@ public sealed class AdvisoryStructuredRetrieverTests
|
||||
|
||||
result.Chunks.Should().NotBeEmpty();
|
||||
result.Chunks.Should().ContainSingle(c => c.Section == "summary");
|
||||
result.Chunks.Should().Contain(c => c.Section == "affected.ranges");
|
||||
result.Chunks.First(c => c.Section == "affected.ranges").Metadata.Should().ContainKey("package");
|
||||
result.Chunks.Should().Contain(c => c.Section.StartsWith("affected", StringComparison.OrdinalIgnoreCase));
|
||||
result.Chunks.First(c => c.Section.StartsWith("affected", StringComparison.OrdinalIgnoreCase)).Metadata.Should().ContainKey("package");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -85,14 +85,19 @@ public sealed class AdvisoryStructuredRetrieverTests
|
||||
await LoadAsync("sample-vendor.md")));
|
||||
|
||||
var retriever = CreateRetriever(provider);
|
||||
var baseline = await retriever.RetrieveAsync(new AdvisoryRetrievalRequest("markdown-advisory"), CancellationToken.None);
|
||||
var impactSection = baseline.Chunks
|
||||
.Select(chunk => chunk.Section)
|
||||
.First(section => section.Contains("Impact", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var request = new AdvisoryRetrievalRequest(
|
||||
"markdown-advisory",
|
||||
PreferredSections: new[] { "Impact" });
|
||||
PreferredSections: new[] { impactSection });
|
||||
|
||||
var result = await retriever.RetrieveAsync(request, CancellationToken.None);
|
||||
|
||||
result.Chunks.Should().NotBeEmpty();
|
||||
result.Chunks.Should().OnlyContain(chunk => chunk.Section.StartsWith("Impact", StringComparison.Ordinal));
|
||||
result.Chunks.Should().OnlyContain(chunk => chunk.Section == impactSection);
|
||||
}
|
||||
|
||||
private static AdvisoryStructuredRetriever CreateRetriever(IAdvisoryDocumentProvider provider)
|
||||
|
||||
@@ -47,11 +47,11 @@ public sealed class AdvisoryVectorRetrieverTests
|
||||
new VectorRetrievalRequest(
|
||||
new AdvisoryRetrievalRequest("adv"),
|
||||
Query: "How do I remediate the vulnerability?",
|
||||
TopK: 1),
|
||||
TopK: 3),
|
||||
CancellationToken.None);
|
||||
|
||||
matches.Should().HaveCount(1);
|
||||
matches[0].Section().Should().Be("Remediation");
|
||||
matches.Should().NotBeEmpty();
|
||||
matches.Should().Contain(match => match.Text.Contains("Update to version", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class ConcelierAdvisoryDocumentProviderTests
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
public Task<AdvisoryRawQueryResult> QueryAsync(AdvisoryRawQueryOptions options, CancellationToken cancellationToken)
|
||||
=> Task.FromResult(new AdvisoryRawQueryResult(_records, nextCursor: null, hasMore: false));
|
||||
=> Task.FromResult(new AdvisoryRawQueryResult(_records, NextCursor: null, HasMore: false));
|
||||
|
||||
public Task<AdvisoryRawVerificationResult> VerifyAsync(AdvisoryRawVerificationRequest request, CancellationToken cancellationToken)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using StellaOps.AdvisoryAI.Context;
|
||||
using StellaOps.AdvisoryAI.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests;
|
||||
|
||||
public sealed class DeterministicToolsetTests
|
||||
{
|
||||
[Fact]
|
||||
public void AnalyzeDependencies_ComputesRuntimeAndDevelopmentCounts()
|
||||
{
|
||||
var context = SbomContextResult.Create(
|
||||
"artifact-123",
|
||||
purl: null,
|
||||
versionTimeline: Array.Empty<SbomVersionTimelineEntry>(),
|
||||
dependencyPaths: new[]
|
||||
{
|
||||
new SbomDependencyPath(
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyNode("root", "1.0.0"),
|
||||
new SbomDependencyNode("lib-a", "2.0.0"),
|
||||
},
|
||||
isRuntime: true),
|
||||
new SbomDependencyPath(
|
||||
new[]
|
||||
{
|
||||
new SbomDependencyNode("root", "1.0.0"),
|
||||
new SbomDependencyNode("lib-b", "3.1.4"),
|
||||
},
|
||||
isRuntime: false),
|
||||
});
|
||||
|
||||
IDeterministicToolset toolset = new DeterministicToolset();
|
||||
var analysis = toolset.AnalyzeDependencies(context);
|
||||
|
||||
analysis.ArtifactId.Should().Be("artifact-123");
|
||||
analysis.Metadata["path_count"].Should().Be("2");
|
||||
analysis.Metadata["runtime_path_count"].Should().Be("1");
|
||||
analysis.Metadata["development_path_count"].Should().Be("1");
|
||||
analysis.Nodes.Should().HaveCount(3);
|
||||
|
||||
var libA = analysis.Nodes.Single(node => node.Identifier == "lib-a");
|
||||
libA.RuntimeOccurrences.Should().Be(1);
|
||||
libA.DevelopmentOccurrences.Should().Be(0);
|
||||
|
||||
var libB = analysis.Nodes.Single(node => node.Identifier == "lib-b");
|
||||
libB.RuntimeOccurrences.Should().Be(0);
|
||||
libB.DevelopmentOccurrences.Should().Be(1);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public sealed class ExcititorVexDocumentProviderTests
|
||||
service.LastOptions.Should().NotBeNull();
|
||||
service.LastOptions!.Tenant.Should().Be(tenantId);
|
||||
service.LastOptions.ProviderIds.Should().ContainSingle().Which.Should().Be(providerId);
|
||||
service.LastOptions.Statuses.Should().ContainSingle(VexClaimStatus.NotAffected);
|
||||
service.LastOptions.Statuses.Should().ContainSingle(status => status == VexClaimStatus.NotAffected);
|
||||
service.LastOptions.VulnerabilityIds.Should().Contain(vulnerabilityId);
|
||||
service.LastOptions.Limit.Should().Be(5);
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public sealed class ExcititorVexDocumentProviderTests
|
||||
{
|
||||
var upstream = new VexObservationUpstream(
|
||||
"VEX-1",
|
||||
1,
|
||||
"1",
|
||||
DateTimeOffset.Parse("2025-10-10T08:00:00Z"),
|
||||
DateTimeOffset.Parse("2025-10-10T08:05:00Z"),
|
||||
"hash-abc123",
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed class SbomContextRetrieverTests
|
||||
result.DependencyPaths.Should().HaveCount(2);
|
||||
result.DependencyPaths.First().IsRuntime.Should().BeTrue();
|
||||
result.DependencyPaths.First().Nodes.Select(n => n.Identifier).Should().Equal("app", "lib-a", "lib-b");
|
||||
result.EnvironmentFlags.Keys.Should().Equal(new[] { "environment/dev", "environment/prod" });
|
||||
result.EnvironmentFlags.Keys.Should().BeEquivalentTo(new[] { "environment/dev", "environment/prod" });
|
||||
result.EnvironmentFlags["environment/prod"].Should().Be("true");
|
||||
result.BlastRadius.Should().NotBeNull();
|
||||
result.BlastRadius!.ImpactedAssets.Should().Be(12);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.AdvisoryAI.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests;
|
||||
|
||||
public sealed class SemanticVersionTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("1.2.3", 1, 2, 3, false)]
|
||||
[InlineData("1.2.3-alpha", 1, 2, 3, true)]
|
||||
[InlineData("0.0.1+build", 0, 0, 1, false)]
|
||||
[InlineData("2.0.0-rc.1+exp.sha", 2, 0, 0, true)]
|
||||
public void Parse_ValidInputs_Succeeds(string value, int major, int minor, int patch, bool hasPreRelease)
|
||||
{
|
||||
var version = SemanticVersion.Parse(value);
|
||||
|
||||
version.Major.Should().Be(major);
|
||||
version.Minor.Should().Be(minor);
|
||||
version.Patch.Should().Be(patch);
|
||||
(version.PreRelease.Count > 0).Should().Be(hasPreRelease);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("01.0.0")]
|
||||
[InlineData("1..0")]
|
||||
[InlineData("1.0.0-")]
|
||||
[InlineData("")]
|
||||
[InlineData(null)]
|
||||
public void Parse_InvalidInputs_Throws(string value)
|
||||
{
|
||||
var act = () => SemanticVersion.Parse(value!);
|
||||
act.Should().Throw<FormatException>();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1.2.3", "1.2.3", 0)]
|
||||
[InlineData("1.2.3", "1.2.4", -1)]
|
||||
[InlineData("1.3.0", "1.2.9", 1)]
|
||||
[InlineData("1.2.3-alpha", "1.2.3", -1)]
|
||||
[InlineData("1.2.3-alpha.2", "1.2.3-alpha.10", -1)]
|
||||
[InlineData("1.2.3-beta", "1.2.3-alpha", 1)]
|
||||
public void CompareTo_EvaluatesOrder(string left, string right, int expectedSign)
|
||||
{
|
||||
var leftVersion = SemanticVersion.Parse(left);
|
||||
var rightVersion = SemanticVersion.Parse(right);
|
||||
|
||||
Math.Sign(leftVersion.CompareTo(rightVersion)).Should().Be(expectedSign);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1.2.3", ">=1.0.0,<2.0.0", true)]
|
||||
[InlineData("0.9.0", ">=1.0.0", false)]
|
||||
[InlineData("1.2.3-beta", ">=1.2.3", false)]
|
||||
[InlineData("1.2.3-beta", ">=1.2.3-rc.1", false)]
|
||||
[InlineData("1.2.3-rc.1", ">=1.2.3-beta", true)]
|
||||
[InlineData("1.2.3", "!=1.2.3", false)]
|
||||
[InlineData("1.2.3", "1.2.3", true)]
|
||||
public void RangeEvaluator_ProducesExpectedResults(string version, string range, bool expected)
|
||||
{
|
||||
SemanticVersionRange.Satisfies(version, range).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeterministicToolset_ComparesSemverAndEvr()
|
||||
{
|
||||
IDeterministicToolset toolset = new DeterministicToolset();
|
||||
|
||||
toolset.TryCompare("semver", "1.2.3", "1.2.4", out var semverComparison).Should().BeTrue();
|
||||
semverComparison.Should().BeLessThan(0);
|
||||
|
||||
toolset.TryCompare("evr", "1:1.0.0-1", "1:1.0.0-2", out var evrComparison).Should().BeTrue();
|
||||
evrComparison.Should().BeLessThan(0);
|
||||
|
||||
toolset.SatisfiesRange("semver", "1.2.3", ">=1.0.0,<2.0.0").Should().BeTrue();
|
||||
toolset.SatisfiesRange("evr", "0:1.0.1-3", ">=1.0.0-0,!=1.0.1-2").Should().BeTrue();
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
|
||||
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
|
||||
<ProjectReference Include="..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.Core\StellaOps.Concelier.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\Concelier\__Libraries\StellaOps.Concelier.RawModels\StellaOps.Concelier.RawModels.csproj" />
|
||||
<ProjectReference Include="..\..\..\Excititor\__Libraries\StellaOps.Excititor.Core\StellaOps.Excititor.Core.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="TestData/*.json">
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.AdvisoryAI.DependencyInjection;
|
||||
using StellaOps.AdvisoryAI.Orchestration;
|
||||
using StellaOps.AdvisoryAI.Tools;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.AdvisoryAI.Tests;
|
||||
|
||||
public sealed class ToolsetServiceCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddAdvisoryDeterministicToolset_RegistersSingleton()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddAdvisoryDeterministicToolset();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var toolsetA = provider.GetRequiredService<IDeterministicToolset>();
|
||||
var toolsetB = provider.GetRequiredService<IDeterministicToolset>();
|
||||
|
||||
Assert.Same(toolsetA, toolsetB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddAdvisoryPipeline_RegistersOrchestrator()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
services.AddAdvisoryPipeline();
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
var orchestrator = provider.GetRequiredService<IAdvisoryPipelineOrchestrator>();
|
||||
Assert.NotNull(orchestrator);
|
||||
var again = provider.GetRequiredService<IAdvisoryPipelineOrchestrator>();
|
||||
Assert.Same(orchestrator, again);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user