sprints work.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryAnalysisDoctorPluginTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-001 - Binary Analysis Doctor Plugin Scaffold
|
||||
// Description: Unit tests for BinaryAnalysisDoctorPlugin
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class BinaryAnalysisDoctorPluginTests
|
||||
{
|
||||
private readonly BinaryAnalysisDoctorPlugin _plugin = new();
|
||||
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
// Assert
|
||||
_plugin.PluginId.Should().Be("stellaops.doctor.binaryanalysis");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_IsSecurity()
|
||||
{
|
||||
// Assert
|
||||
_plugin.Category.Should().Be(DoctorCategory.Security);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_IsBinaryAnalysis()
|
||||
{
|
||||
// Assert
|
||||
_plugin.DisplayName.Should().Be("Binary Analysis");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue_Always()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
// Act & Assert
|
||||
_plugin.IsAvailable(services).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsAtLeastOneCheck()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Should().NotBeEmpty();
|
||||
checks.Should().HaveCountGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDebuginfodCheck()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.debuginfod.available");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDdebCheck()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.ddeb.enabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsBuildinfoCacheCheck()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.buildinfo.cache");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSymbolRecoveryFallbackCheck()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.symbol.recovery.fallback");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsFourChecks()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var checks = _plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Should().HaveCount(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesWithoutError()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act & Assert
|
||||
await _plugin.Invoking(p => p.InitializeAsync(context, CancellationToken.None))
|
||||
.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_IsNotNull()
|
||||
{
|
||||
// Assert
|
||||
_plugin.Version.Should().NotBeNull();
|
||||
_plugin.Version.Major.Should().BeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_IsNotNull()
|
||||
{
|
||||
// Assert
|
||||
_plugin.MinEngineVersion.Should().NotBeNull();
|
||||
_plugin.MinEngineVersion.Major.Should().BeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContext()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BuildinfoCacheCheckTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-004 - Buildinfo Cache Check
|
||||
// Description: Unit tests for BuildinfoCacheCheck
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugin.BinaryAnalysis.Checks;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests.Checks;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class BuildinfoCacheCheckTests
|
||||
{
|
||||
private readonly BuildinfoCacheCheck _check = new();
|
||||
|
||||
[Fact]
|
||||
public void CheckId_ReturnsExpectedValue()
|
||||
{
|
||||
// Assert
|
||||
_check.CheckId.Should().Be("check.binaryanalysis.buildinfo.cache");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_ReturnsDebianBuildinfoCache()
|
||||
{
|
||||
// Assert
|
||||
_check.Name.Should().Be("Debian Buildinfo Cache");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultSeverity_IsWarn()
|
||||
{
|
||||
// Assert
|
||||
_check.DefaultSeverity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tags_ContainsBuildinfo()
|
||||
{
|
||||
// Assert
|
||||
_check.Tags.Should().Contain("buildinfo");
|
||||
_check.Tags.Should().Contain("debian");
|
||||
_check.Tags.Should().Contain("cache");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRun_ReturnsTrue_Always()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act & Assert
|
||||
_check.CanRun(context).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesVerificationCommand()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.VerificationCommand.Should().NotBeNullOrEmpty();
|
||||
result.VerificationCommand.Should().Contain("stella doctor --check");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsWarningOrPass_WhenServicesReachable()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert - should be Pass, Info, or Warn (not Fail) when services are reachable
|
||||
result.Severity.Should().NotBe(DoctorSeverity.Fail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesEvidence_WithServiceStatus()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Evidence.Should().NotBeNull();
|
||||
result.Evidence.Data.Should().ContainKey("buildinfos_debian_net_reachable");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsFailOrWarn_WhenServicesUnreachable()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(throwException: true);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert - should be Fail or Warn when services unreachable
|
||||
result.Severity.Should().BeOneOf(DoctorSeverity.Fail, DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EstimatedDuration_IsReasonable()
|
||||
{
|
||||
// Assert
|
||||
_check.EstimatedDuration.Should().BeGreaterThan(TimeSpan.Zero);
|
||||
_check.EstimatedDuration.Should().BeLessThanOrEqualTo(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Description_IsNotEmpty()
|
||||
{
|
||||
// Assert
|
||||
_check.Description.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContext()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContextWithHttpClient(Mock<HttpMessageHandler> mockHandler)
|
||||
{
|
||||
var httpClient = new HttpClient(mockHandler.Object);
|
||||
var mockFactory = new Mock<IHttpClientFactory>();
|
||||
mockFactory.Setup(f => f.CreateClient(It.IsAny<string>())).Returns(httpClient);
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var services = new ServiceCollection()
|
||||
.AddSingleton(mockFactory.Object)
|
||||
.BuildServiceProvider();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static Mock<HttpMessageHandler> CreateMockHttpHandler(
|
||||
HttpStatusCode statusCode = HttpStatusCode.OK,
|
||||
bool throwException = false)
|
||||
{
|
||||
var mockHandler = new Mock<HttpMessageHandler>();
|
||||
|
||||
if (throwException)
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ThrowsAsync(new HttpRequestException("Connection refused"));
|
||||
}
|
||||
else
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
return mockHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// DdebRepoEnabledCheckTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-003 - Ddeb Repository Check
|
||||
// Description: Unit tests for DdebRepoEnabledCheck
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugin.BinaryAnalysis.Checks;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests.Checks;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class DdebRepoEnabledCheckTests
|
||||
{
|
||||
private readonly DdebRepoEnabledCheck _check = new();
|
||||
|
||||
[Fact]
|
||||
public void CheckId_ReturnsExpectedValue()
|
||||
{
|
||||
// Assert
|
||||
_check.CheckId.Should().Be("check.binaryanalysis.ddeb.enabled");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_ReturnsUbuntuDdebRepository()
|
||||
{
|
||||
// Assert
|
||||
_check.Name.Should().Be("Ubuntu Ddeb Repository");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultSeverity_IsWarn()
|
||||
{
|
||||
// Assert
|
||||
_check.DefaultSeverity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tags_ContainsDdeb()
|
||||
{
|
||||
// Assert
|
||||
_check.Tags.Should().Contain("ddeb");
|
||||
_check.Tags.Should().Contain("ubuntu");
|
||||
_check.Tags.Should().Contain("binaryanalysis");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRun_ReturnsFalse_OnWindows()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act
|
||||
var canRun = _check.CanRun(context);
|
||||
|
||||
// Assert
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
canRun.Should().BeFalse();
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
canRun.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsSkip_OnNonLinux()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Skip this test on Linux
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Skip);
|
||||
result.Diagnosis.Should().Contain("Linux");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EstimatedDuration_IsReasonable()
|
||||
{
|
||||
// Assert
|
||||
_check.EstimatedDuration.Should().BeGreaterThan(TimeSpan.Zero);
|
||||
_check.EstimatedDuration.Should().BeLessThanOrEqualTo(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Description_IsNotEmpty()
|
||||
{
|
||||
// Assert
|
||||
_check.Description.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContext()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// DebuginfodAvailabilityCheckTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-002 - Debuginfod Availability Check
|
||||
// Description: Unit tests for DebuginfodAvailabilityCheck with mocked HTTP
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugin.BinaryAnalysis.Checks;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests.Checks;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class DebuginfodAvailabilityCheckTests
|
||||
{
|
||||
private readonly DebuginfodAvailabilityCheck _check = new();
|
||||
|
||||
[Fact]
|
||||
public void CheckId_ReturnsExpectedValue()
|
||||
{
|
||||
// Assert
|
||||
_check.CheckId.Should().Be("check.binaryanalysis.debuginfod.available");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_ReturnsDebuginfodAvailability()
|
||||
{
|
||||
// Assert
|
||||
_check.Name.Should().Be("Debuginfod Availability");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultSeverity_IsWarn()
|
||||
{
|
||||
// Assert
|
||||
_check.DefaultSeverity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tags_ContainsBinaryAnalysis()
|
||||
{
|
||||
// Assert
|
||||
_check.Tags.Should().Contain("binaryanalysis");
|
||||
_check.Tags.Should().Contain("debuginfod");
|
||||
_check.Tags.Should().Contain("symbols");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRun_ReturnsTrue_Always()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act & Assert
|
||||
_check.CanRun(context).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsPass_WhenDebuginfodReachable()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Set environment variable for test
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://debuginfod.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Pass);
|
||||
result.Diagnosis.Should().Contain("reachable");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsInfo_WhenDefaultUrlReachableButEnvNotSet()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", null);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Info);
|
||||
result.Diagnosis.Should().Contain("not configured");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsFail_WhenAllUrlsUnreachable()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(throwException: true);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://debuginfod.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Fail);
|
||||
result.Diagnosis.Should().Contain("None");
|
||||
result.Diagnosis.Should().Contain("reachable");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesRemediationSteps_OnFailure()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(throwException: true);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://debuginfod.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Remediation.Should().NotBeNull();
|
||||
result.Remediation!.Steps.Should().NotBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ParsesMultipleUrls()
|
||||
{
|
||||
// Arrange - all will return OK since we use the same mock
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS",
|
||||
"https://debuginfod1.example.com https://debuginfod2.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Pass);
|
||||
result.Diagnosis.Should().Contain("2"); // Should mention 2 URLs
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesVerificationCommand()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://debuginfod.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.VerificationCommand.Should().NotBeNullOrEmpty();
|
||||
result.VerificationCommand.Should().Contain("stella doctor --check");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContext()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContextWithHttpClient(Mock<HttpMessageHandler> mockHandler)
|
||||
{
|
||||
var httpClient = new HttpClient(mockHandler.Object);
|
||||
var mockFactory = new Mock<IHttpClientFactory>();
|
||||
mockFactory.Setup(f => f.CreateClient(It.IsAny<string>())).Returns(httpClient);
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var services = new ServiceCollection()
|
||||
.AddSingleton(mockFactory.Object)
|
||||
.BuildServiceProvider();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static Mock<HttpMessageHandler> CreateMockHttpHandler(
|
||||
HttpStatusCode statusCode = HttpStatusCode.OK,
|
||||
bool throwException = false)
|
||||
{
|
||||
var mockHandler = new Mock<HttpMessageHandler>();
|
||||
|
||||
if (throwException)
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ThrowsAsync(new HttpRequestException("Connection refused"));
|
||||
}
|
||||
else
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
return mockHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// SymbolRecoveryFallbackCheckTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-005 - Symbol Recovery Fallback Check
|
||||
// Description: Unit tests for SymbolRecoveryFallbackCheck
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugin.BinaryAnalysis.Checks;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests.Checks;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class SymbolRecoveryFallbackCheckTests
|
||||
{
|
||||
private readonly SymbolRecoveryFallbackCheck _check = new();
|
||||
|
||||
[Fact]
|
||||
public void CheckId_ReturnsExpectedValue()
|
||||
{
|
||||
// Assert
|
||||
_check.CheckId.Should().Be("check.binaryanalysis.symbol.recovery.fallback");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Name_ReturnsSymbolRecoveryFallback()
|
||||
{
|
||||
// Assert
|
||||
_check.Name.Should().Be("Symbol Recovery Fallback");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultSeverity_IsWarn()
|
||||
{
|
||||
// Assert
|
||||
_check.DefaultSeverity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Tags_ContainsMeta()
|
||||
{
|
||||
// Assert
|
||||
_check.Tags.Should().Contain("meta");
|
||||
_check.Tags.Should().Contain("fallback");
|
||||
_check.Tags.Should().Contain("symbols");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanRun_ReturnsTrue_Always()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContext();
|
||||
|
||||
// Act & Assert
|
||||
_check.CanRun(context).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesVerificationCommand()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.VerificationCommand.Should().NotBeNullOrEmpty();
|
||||
result.VerificationCommand.Should().Contain("stella doctor --check");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesEvidence_WithSourceCounts()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Evidence.Should().NotBeNull();
|
||||
result.Evidence.Data.Should().ContainKey("total_sources_checked");
|
||||
result.Evidence.Data.Should().ContainKey("available_sources");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_AggregatesChildCheckResults()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert - should have evidence for multiple sources
|
||||
result.Evidence.Data.Keys.Should().Contain(k => k.StartsWith("source_"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsPassOrInfo_WhenAtLeastOneSourceAvailable()
|
||||
{
|
||||
// Arrange - at least debuginfod should succeed with mocked HTTP
|
||||
var mockHandler = CreateMockHttpHandler(HttpStatusCode.OK);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Set DEBUGINFOD_URLS to ensure at least one source is available
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://debuginfod.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert - should be Pass or Info when at least one source is available
|
||||
result.Severity.Should().BeOneOf(DoctorSeverity.Pass, DoctorSeverity.Info);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReturnsFail_WhenNoSourcesAvailable()
|
||||
{
|
||||
// Arrange - all HTTP calls will fail
|
||||
var mockHandler = CreateMockHttpHandler(throwException: true);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
// Ensure no DEBUGINFOD_URLS fallback
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://unreachable.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Severity.Should().Be(DoctorSeverity.Fail);
|
||||
result.Diagnosis.Should().Contain("No symbol recovery sources");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_IncludesRemediation_WhenNoSourcesAvailable()
|
||||
{
|
||||
// Arrange
|
||||
var mockHandler = CreateMockHttpHandler(throwException: true);
|
||||
var context = CreateContextWithHttpClient(mockHandler);
|
||||
|
||||
var originalValue = Environment.GetEnvironmentVariable("DEBUGINFOD_URLS");
|
||||
try
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", "https://unreachable.example.com");
|
||||
|
||||
// Act
|
||||
var result = await _check.RunAsync(context, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.Remediation.Should().NotBeNull();
|
||||
result.Remediation!.Steps.Should().NotBeEmpty();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Environment.SetEnvironmentVariable("DEBUGINFOD_URLS", originalValue);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EstimatedDuration_IsLargerThanChildChecks()
|
||||
{
|
||||
// The fallback check runs multiple child checks, so should have larger duration
|
||||
_check.EstimatedDuration.Should().BeGreaterThanOrEqualTo(TimeSpan.FromSeconds(30));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Description_IsNotEmpty()
|
||||
{
|
||||
// Assert
|
||||
_check.Description.Should().NotBeNullOrEmpty();
|
||||
_check.Description.Should().Contain("at least one");
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContext()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = new ServiceCollection().BuildServiceProvider(),
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateContextWithHttpClient(Mock<HttpMessageHandler> mockHandler)
|
||||
{
|
||||
var httpClient = new HttpClient(mockHandler.Object);
|
||||
var mockFactory = new Mock<IHttpClientFactory>();
|
||||
mockFactory.Setup(f => f.CreateClient(It.IsAny<string>())).Returns(httpClient);
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var services = new ServiceCollection()
|
||||
.AddSingleton(mockFactory.Object)
|
||||
.BuildServiceProvider();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
}
|
||||
|
||||
private static Mock<HttpMessageHandler> CreateMockHttpHandler(
|
||||
HttpStatusCode statusCode = HttpStatusCode.OK,
|
||||
bool throwException = false)
|
||||
{
|
||||
var mockHandler = new Mock<HttpMessageHandler>();
|
||||
|
||||
if (throwException)
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ThrowsAsync(new HttpRequestException("Connection refused"));
|
||||
}
|
||||
else
|
||||
{
|
||||
mockHandler
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.IsAny<HttpRequestMessage>(),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.ReturnsAsync(new HttpResponseMessage
|
||||
{
|
||||
StatusCode = statusCode
|
||||
});
|
||||
}
|
||||
|
||||
return mockHandler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// BinaryAnalysisPluginIntegrationTests.cs
|
||||
// Sprint: SPRINT_20260119_003_Doctor_binary_analysis_checks
|
||||
// Task: DBIN-006, DBIN-007 - Integration tests for plugin discovery and CLI behavior
|
||||
// Description: Verifies plugin integration with Doctor engine and CLI filtering
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugin.BinaryAnalysis.DependencyInjection;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugin.BinaryAnalysis.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests verifying plugin registration and discovery behavior.
|
||||
/// </summary>
|
||||
[Trait("Category", "Integration")]
|
||||
public class BinaryAnalysisPluginIntegrationTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddDoctorBinaryAnalysisPlugin_RegistersPluginAsSingleton()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Act
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// Assert
|
||||
var plugins = provider.GetServices<IDoctorPlugin>().ToList();
|
||||
plugins.Should().ContainSingle(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDoctorBinaryAnalysisPlugin_PluginHasSecurityCategory()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
// Assert
|
||||
plugin.Category.Should().Be(DoctorCategory.Security);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsFourBinaryAnalysisChecks()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var context = new DoctorPluginContext
|
||||
{
|
||||
Services = provider,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
|
||||
// Act
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
checks.Should().HaveCount(4);
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.debuginfod.available");
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.ddeb.enabled");
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.buildinfo.cache");
|
||||
checks.Select(c => c.CheckId).Should().Contain("check.binaryanalysis.symbol.recovery.fallback");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllChecks_HaveBinaryanalysisTag()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var context = new DoctorPluginContext
|
||||
{
|
||||
Services = provider,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
|
||||
// Act
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
foreach (var check in checks)
|
||||
{
|
||||
check.Tags.Should().Contain("binaryanalysis",
|
||||
because: $"check {check.CheckId} should have binaryanalysis tag for CLI filtering");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllChecks_HaveSecurityTag()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var context = new DoctorPluginContext
|
||||
{
|
||||
Services = provider,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
|
||||
// Act
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
foreach (var check in checks)
|
||||
{
|
||||
check.Tags.Should().Contain("security",
|
||||
because: $"check {check.CheckId} should have security tag for CLI filtering");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllChecks_HaveValidCheckIdFormat()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
var config = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
|
||||
var context = new DoctorPluginContext
|
||||
{
|
||||
Services = provider,
|
||||
Configuration = config,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = config.GetSection("Doctor:Plugins")
|
||||
};
|
||||
|
||||
// Act
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
// Assert
|
||||
foreach (var check in checks)
|
||||
{
|
||||
check.CheckId.Should().StartWith("check.binaryanalysis.",
|
||||
"check IDs should follow check.<category>.<name> convention");
|
||||
check.CheckId.Should().NotContain(" ");
|
||||
check.CheckId.Should().NotContain("\t");
|
||||
check.CheckId.Should().NotContain("\n");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plugin_CanFilterByCategory_Security()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// Act - simulate CLI --category Security filter
|
||||
var plugins = provider.GetServices<IDoctorPlugin>()
|
||||
.Where(p => p.Category == DoctorCategory.Security)
|
||||
.ToList();
|
||||
|
||||
// Assert
|
||||
plugins.Should().Contain(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plugin_HasVersionInfo()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection();
|
||||
services.AddDoctorBinaryAnalysisPlugin();
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
// Act
|
||||
var plugin = provider.GetServices<IDoctorPlugin>()
|
||||
.Single(p => p.PluginId == "stellaops.doctor.binaryanalysis");
|
||||
|
||||
// Assert
|
||||
plugin.Version.Should().NotBeNull();
|
||||
plugin.Version.Major.Should().BeGreaterThanOrEqualTo(1);
|
||||
plugin.MinEngineVersion.Should().NotBeNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\__Plugins\StellaOps.Doctor.Plugin.BinaryAnalysis\StellaOps.Doctor.Plugin.BinaryAnalysis.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user