doctor: complete runtime check documentation sprint

Signed-off-by: master <>
This commit is contained in:
master
2026-03-31 23:26:24 +03:00
parent 404d50bcb7
commit 152c1b1357
54 changed files with 2210 additions and 258 deletions

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Moq" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Doctor\StellaOps.Doctor.csproj" />
<ProjectReference Include="..\..\StellaOps.Doctor.Plugins.Verification\StellaOps.Doctor.Plugins.Verification.csproj" />
<ProjectReference Include="..\..\StellaOps.TestKit\StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,80 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
using StellaOps.Doctor.Plugins.Verification.Checks;
using Xunit;
namespace StellaOps.Doctor.Plugins.Verification.Tests;
[Trait("Category", "Unit")]
public sealed class VerificationCheckRunbookTests
{
[Theory]
[InlineData("artifact", "docs/doctor/articles/verification/verification-artifact-pull.md")]
[InlineData("signature", "docs/doctor/articles/verification/verification-signature.md")]
[InlineData("sbom", "docs/doctor/articles/verification/verification-sbom-validation.md")]
[InlineData("vex", "docs/doctor/articles/verification/verification-vex-validation.md")]
[InlineData("policy", "docs/doctor/articles/verification/verification-policy-engine.md")]
public async Task RunAsync_WhenOfflineBundleMissing_UsesExpectedRunbook(string checkName, string expectedRunbook)
{
var check = CreateCheck(checkName);
var context = CreateContext(new Dictionary<string, string?>
{
["Doctor:Plugins:Verification:Enabled"] = "true",
["Doctor:Plugins:Verification:TestArtifact:OfflineBundlePath"] = Path.Combine(Path.GetTempPath(), $"missing-{Guid.NewGuid():N}.json")
});
var result = await check.RunAsync(context, CancellationToken.None);
Assert.Equal(DoctorSeverity.Fail, result.Severity);
Assert.Equal(expectedRunbook, result.Remediation?.RunbookUrl);
}
[Fact]
public async Task RunAsync_WhenArtifactNotConfigured_UsesBaseRunbook()
{
var check = new SignatureVerificationCheck();
var context = CreateContext(new Dictionary<string, string?>
{
["Doctor:Plugins:Verification:Enabled"] = "true"
});
var result = await check.RunAsync(context, CancellationToken.None);
Assert.Equal(DoctorSeverity.Skip, result.Severity);
Assert.Equal("docs/doctor/articles/verification/verification-signature.md", result.Remediation?.RunbookUrl);
}
private static IDoctorCheck CreateCheck(string checkName) => checkName switch
{
"artifact" => new TestArtifactPullCheck(),
"signature" => new SignatureVerificationCheck(),
"sbom" => new SbomValidationCheck(),
"vex" => new VexValidationCheck(),
"policy" => new PolicyEngineCheck(),
_ => throw new ArgumentOutOfRangeException(nameof(checkName), checkName, "Unknown check")
};
private static DoctorPluginContext CreateContext(Dictionary<string, string?> values)
{
var config = new ConfigurationBuilder()
.AddInMemoryCollection(values)
.Build();
return new DoctorPluginContext
{
Services = new EmptyServiceProvider(),
Configuration = config,
TimeProvider = TimeProvider.System,
Logger = NullLogger.Instance,
EnvironmentName = "Test",
PluginConfig = config.GetSection("Doctor:Plugins:Verification")
};
}
private sealed class EmptyServiceProvider : IServiceProvider
{
public object? GetService(Type serviceType) => null;
}
}