audit work, doctors work
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Core.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class CorePluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.core", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
|
||||
Assert.Equal("Core Platform", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsCore()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.Core, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsNineChecks()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(9, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsConfigurationLoadedCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.config.loaded");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsRequiredSettingsCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.config.required");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsEnvironmentVariablesCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.env.variables");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDiskSpaceCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.env.diskspace");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsMemoryUsageCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.env.memory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsServiceHealthCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.services.health");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDependencyServicesCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.services.dependencies");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsAuthenticationConfigCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.auth.config");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsCryptoProvidersCheck()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.core.crypto.available");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new CorePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:Core")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Database;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Database.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class DatabasePluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.database", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
|
||||
Assert.Equal("Database", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsDatabase()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.Database, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsEightChecks()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(8, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDatabaseConnectionCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.connection");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsPendingMigrationsCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.migrations.pending");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsFailedMigrationsCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.migrations.failed");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSchemaVersionCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.schema.version");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsConnectionPoolHealthCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.pool.health");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsConnectionPoolSizeCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.pool.size");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsQueryLatencyCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.latency");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsDatabasePermissionsCheck()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.db.permissions");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new DatabasePlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:Database")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Integration;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Integration.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class IntegrationPluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.integration", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
|
||||
Assert.Equal("External Integrations", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsIntegration()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.Integration, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsEightChecks()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(8, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsOciRegistryCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.oci.registry");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsObjectStorageCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.s3.storage");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSmtpCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.smtp");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSlackWebhookCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.slack");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsTeamsWebhookCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.teams");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsGitProviderCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.git");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsLdapConnectivityCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.ldap");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsOidcProviderCheck()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.integration.oidc");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new IntegrationPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:Integration")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Observability;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Observability.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ObservabilityPluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.observability", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
|
||||
Assert.Equal("Observability", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsObservability()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.Observability, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsSixChecks()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(6, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsOpenTelemetryCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.otel");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsLoggingConfigurationCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.logging");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsMetricsCollectionCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.metrics");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsTracingConfigurationCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.tracing");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsHealthCheckEndpointsCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.healthchecks");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsAlertingConfigurationCheck()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.observability.alerting");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new ObservabilityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:Observability")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.Security;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.Security.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class SecurityPluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.security", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
|
||||
Assert.Equal("Security Configuration", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsSecurity()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.Security, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsTenChecks()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(10, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsTlsCertificateCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.tls.certificate");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsJwtConfigurationCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.jwt.config");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsCorsConfigurationCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.cors");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsRateLimitingCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.ratelimit");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSecurityHeadersCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.headers");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsSecretsConfigurationCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.secrets");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsEncryptionKeyCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.encryption");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsPasswordPolicyCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.password.policy");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsAuditLoggingCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.audit.logging");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsApiKeySecurityCheck()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.security.apikey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new SecurityPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:Security")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using StellaOps.Doctor.Plugins.ServiceGraph;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Plugins.ServiceGraph.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public class ServiceGraphPluginTests
|
||||
{
|
||||
[Fact]
|
||||
public void PluginId_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
|
||||
Assert.Equal("stellaops.doctor.servicegraph", plugin.PluginId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisplayName_ReturnsExpectedValue()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
|
||||
Assert.Equal("Service Graph", plugin.DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Category_ReturnsServiceGraph()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
|
||||
Assert.Equal(DoctorCategory.ServiceGraph, plugin.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Version_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
|
||||
Assert.NotNull(plugin.Version);
|
||||
Assert.True(plugin.Version >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinEngineVersion_ReturnsValidVersion()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
|
||||
Assert.NotNull(plugin.MinEngineVersion);
|
||||
Assert.True(plugin.MinEngineVersion >= new Version(1, 0, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAvailable_ReturnsTrue()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
|
||||
Assert.True(plugin.IsAvailable(services));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ReturnsSixChecks()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Equal(6, checks.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsBackendConnectivityCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.backend");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsValkeyConnectivityCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.valkey");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsMessageQueueCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.mq");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsServiceEndpointsCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.endpoints");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsCircuitBreakerStatusCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.circuitbreaker");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetChecks_ContainsServiceTimeoutCheck()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
var checks = plugin.GetChecks(context);
|
||||
|
||||
Assert.Contains(checks, c => c.CheckId == "check.servicegraph.timeouts");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InitializeAsync_CompletesSuccessfully()
|
||||
{
|
||||
var plugin = new ServiceGraphPlugin();
|
||||
var context = CreateTestContext();
|
||||
|
||||
await plugin.InitializeAsync(context, CancellationToken.None);
|
||||
|
||||
// Should complete without throwing
|
||||
}
|
||||
|
||||
private static DoctorPluginContext CreateTestContext(IConfiguration? configuration = null)
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
configuration ??= new ConfigurationBuilder().Build();
|
||||
|
||||
return new DoctorPluginContext
|
||||
{
|
||||
Services = services,
|
||||
Configuration = configuration,
|
||||
TimeProvider = TimeProvider.System,
|
||||
Logger = NullLogger.Instance,
|
||||
EnvironmentName = "Test",
|
||||
PluginConfig = configuration.GetSection("Doctor:Plugins:ServiceGraph")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
// <copyright file="DoctorEngineTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DoctorEngineTests.cs
|
||||
// Sprint: SPRINT_20260112_001_001_DOCTOR_foundation
|
||||
// Task: DOC-FND-008 - Doctor engine unit tests
|
||||
// Description: Tests for the DoctorEngine orchestrator.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using StellaOps.Doctor.DependencyInjection;
|
||||
using StellaOps.Doctor.Engine;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Plugins;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.Engine;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DoctorEngineTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task RunAsync_WithNoPlugins_ReturnsEmptyReport()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.Should().NotBeNull();
|
||||
report.Summary.Total.Should().Be(0);
|
||||
report.OverallSeverity.Should().Be(DoctorSeverity.Pass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_GeneratesUniqueRunId()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var report1 = await engine.RunAsync();
|
||||
var report2 = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report1.RunId.Should().NotBeNullOrEmpty();
|
||||
report2.RunId.Should().NotBeNullOrEmpty();
|
||||
report1.RunId.Should().NotBe(report2.RunId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_RunIdStartsWithDrPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.RunId.Should().StartWith("dr_");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_SetsStartAndEndTimes()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.StartedAt.Should().BeBefore(report.CompletedAt);
|
||||
report.Duration.Should().BeGreaterThanOrEqualTo(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithCancellation_RespectsToken()
|
||||
{
|
||||
// Arrange
|
||||
// Use a plugin that takes time, so cancellation can be checked
|
||||
var slowPlugin = CreateSlowMockPlugin();
|
||||
var engine = CreateEngine(slowPlugin);
|
||||
var cts = new CancellationTokenSource();
|
||||
|
||||
// Act - Cancel after a short delay
|
||||
var task = engine.RunAsync(null, null, cts.Token);
|
||||
cts.CancelAfter(TimeSpan.FromMilliseconds(10));
|
||||
|
||||
// Assert - Either throws OperationCanceledException or completes (if too fast)
|
||||
try
|
||||
{
|
||||
var report = await task;
|
||||
// If it completes, we still verify it ran
|
||||
report.Should().NotBeNull();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected if cancellation was honored
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListChecks_WithNoPlugins_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var checks = engine.ListChecks();
|
||||
|
||||
// Assert
|
||||
checks.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ListPlugins_WithNoPlugins_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var plugins = engine.ListPlugins();
|
||||
|
||||
// Assert
|
||||
plugins.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAvailableCategories_WithNoPlugins_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var engine = CreateEngine();
|
||||
|
||||
// Act
|
||||
var categories = engine.GetAvailableCategories();
|
||||
|
||||
// Assert
|
||||
categories.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithMockPlugin_ExecutesChecks()
|
||||
{
|
||||
// Arrange
|
||||
var mockPlugin = new Mock<IDoctorPlugin>();
|
||||
mockPlugin.Setup(p => p.PluginId).Returns("test.plugin");
|
||||
mockPlugin.Setup(p => p.DisplayName).Returns("Test Plugin");
|
||||
mockPlugin.Setup(p => p.Category).Returns(DoctorCategory.Core);
|
||||
mockPlugin.Setup(p => p.Version).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.MinEngineVersion).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.IsAvailable(It.IsAny<IServiceProvider>())).Returns(true);
|
||||
|
||||
var mockCheck = new Mock<IDoctorCheck>();
|
||||
mockCheck.Setup(c => c.CheckId).Returns("check.test.mock");
|
||||
mockCheck.Setup(c => c.Name).Returns("Mock Check");
|
||||
mockCheck.Setup(c => c.Description).Returns("A mock check for testing");
|
||||
mockCheck.Setup(c => c.DefaultSeverity).Returns(DoctorSeverity.Fail);
|
||||
mockCheck.Setup(c => c.Tags).Returns(new[] { "quick" });
|
||||
mockCheck.Setup(c => c.EstimatedDuration).Returns(TimeSpan.FromSeconds(1));
|
||||
mockCheck.Setup(c => c.CanRun(It.IsAny<DoctorPluginContext>())).Returns(true);
|
||||
mockCheck.Setup(c => c.RunAsync(It.IsAny<DoctorPluginContext>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.mock",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Core",
|
||||
Severity = DoctorSeverity.Pass,
|
||||
Diagnosis = "Check passed",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
mockPlugin.Setup(p => p.GetChecks(It.IsAny<DoctorPluginContext>()))
|
||||
.Returns(new[] { mockCheck.Object });
|
||||
|
||||
var engine = CreateEngine(mockPlugin.Object);
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.Summary.Total.Should().Be(1);
|
||||
report.Summary.Passed.Should().Be(1);
|
||||
report.OverallSeverity.Should().Be(DoctorSeverity.Pass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithFailingCheck_ReturnsFailSeverity()
|
||||
{
|
||||
// Arrange
|
||||
var mockPlugin = CreateMockPluginWithCheck(DoctorSeverity.Fail, "Check failed");
|
||||
var engine = CreateEngine(mockPlugin);
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.Summary.Failed.Should().Be(1);
|
||||
report.OverallSeverity.Should().Be(DoctorSeverity.Fail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_WithWarningCheck_ReturnsWarnSeverity()
|
||||
{
|
||||
// Arrange
|
||||
var mockPlugin = CreateMockPluginWithCheck(DoctorSeverity.Warn, "Check had warnings");
|
||||
var engine = CreateEngine(mockPlugin);
|
||||
|
||||
// Act
|
||||
var report = await engine.RunAsync();
|
||||
|
||||
// Assert
|
||||
report.Summary.Warnings.Should().Be(1);
|
||||
report.OverallSeverity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunAsync_ReportsProgress()
|
||||
{
|
||||
// Arrange
|
||||
var mockPlugin = CreateMockPluginWithCheck(DoctorSeverity.Pass, "Check passed");
|
||||
var engine = CreateEngine(mockPlugin);
|
||||
|
||||
var progressReports = new List<DoctorCheckProgress>();
|
||||
var progress = new Progress<DoctorCheckProgress>(p => progressReports.Add(p));
|
||||
|
||||
// Act
|
||||
await engine.RunAsync(null, progress);
|
||||
|
||||
// Allow time for progress to be reported
|
||||
await Task.Delay(100);
|
||||
|
||||
// Assert
|
||||
progressReports.Should().NotBeEmpty();
|
||||
}
|
||||
|
||||
private static DoctorEngine CreateEngine(params IDoctorPlugin[] plugins)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
|
||||
// Add configuration
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.AddInMemoryCollection(new Dictionary<string, string?>())
|
||||
.Build();
|
||||
services.AddSingleton<IConfiguration>(configuration);
|
||||
|
||||
// Add time provider
|
||||
services.AddSingleton(TimeProvider.System);
|
||||
|
||||
// Add logging
|
||||
services.AddLogging();
|
||||
|
||||
// Add doctor services
|
||||
services.AddDoctorEngine();
|
||||
|
||||
// Add mock plugins
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
services.AddSingleton<IDoctorPlugin>(plugin);
|
||||
}
|
||||
|
||||
var provider = services.BuildServiceProvider();
|
||||
return provider.GetRequiredService<DoctorEngine>();
|
||||
}
|
||||
|
||||
private static IDoctorPlugin CreateSlowMockPlugin()
|
||||
{
|
||||
var mockPlugin = new Mock<IDoctorPlugin>();
|
||||
mockPlugin.Setup(p => p.PluginId).Returns("test.slow");
|
||||
mockPlugin.Setup(p => p.DisplayName).Returns("Slow Test Plugin");
|
||||
mockPlugin.Setup(p => p.Category).Returns(DoctorCategory.Core);
|
||||
mockPlugin.Setup(p => p.Version).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.MinEngineVersion).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.IsAvailable(It.IsAny<IServiceProvider>())).Returns(true);
|
||||
|
||||
var mockCheck = new Mock<IDoctorCheck>();
|
||||
mockCheck.Setup(c => c.CheckId).Returns("check.test.slow");
|
||||
mockCheck.Setup(c => c.Name).Returns("Slow Check");
|
||||
mockCheck.Setup(c => c.Description).Returns("A slow check for testing cancellation");
|
||||
mockCheck.Setup(c => c.DefaultSeverity).Returns(DoctorSeverity.Pass);
|
||||
mockCheck.Setup(c => c.Tags).Returns(new[] { "slow" });
|
||||
mockCheck.Setup(c => c.EstimatedDuration).Returns(TimeSpan.FromSeconds(5));
|
||||
mockCheck.Setup(c => c.CanRun(It.IsAny<DoctorPluginContext>())).Returns(true);
|
||||
mockCheck.Setup(c => c.RunAsync(It.IsAny<DoctorPluginContext>(), It.IsAny<CancellationToken>()))
|
||||
.Returns<DoctorPluginContext, CancellationToken>(async (ctx, ct) =>
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), ct);
|
||||
return new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.slow",
|
||||
PluginId = "test.slow",
|
||||
Category = "Core",
|
||||
Severity = DoctorSeverity.Pass,
|
||||
Diagnosis = "Slow check completed",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromSeconds(5),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
});
|
||||
|
||||
mockPlugin.Setup(p => p.GetChecks(It.IsAny<DoctorPluginContext>()))
|
||||
.Returns(new[] { mockCheck.Object });
|
||||
|
||||
return mockPlugin.Object;
|
||||
}
|
||||
|
||||
private static IDoctorPlugin CreateMockPluginWithCheck(DoctorSeverity severity, string diagnosis)
|
||||
{
|
||||
var mockPlugin = new Mock<IDoctorPlugin>();
|
||||
mockPlugin.Setup(p => p.PluginId).Returns("test.plugin");
|
||||
mockPlugin.Setup(p => p.DisplayName).Returns("Test Plugin");
|
||||
mockPlugin.Setup(p => p.Category).Returns(DoctorCategory.Core);
|
||||
mockPlugin.Setup(p => p.Version).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.MinEngineVersion).Returns(new Version(1, 0, 0));
|
||||
mockPlugin.Setup(p => p.IsAvailable(It.IsAny<IServiceProvider>())).Returns(true);
|
||||
|
||||
var mockCheck = new Mock<IDoctorCheck>();
|
||||
mockCheck.Setup(c => c.CheckId).Returns("check.test.mock");
|
||||
mockCheck.Setup(c => c.Name).Returns("Mock Check");
|
||||
mockCheck.Setup(c => c.Description).Returns("A mock check for testing");
|
||||
mockCheck.Setup(c => c.DefaultSeverity).Returns(severity);
|
||||
mockCheck.Setup(c => c.Tags).Returns(new[] { "quick" });
|
||||
mockCheck.Setup(c => c.EstimatedDuration).Returns(TimeSpan.FromSeconds(1));
|
||||
mockCheck.Setup(c => c.CanRun(It.IsAny<DoctorPluginContext>())).Returns(true);
|
||||
mockCheck.Setup(c => c.RunAsync(It.IsAny<DoctorPluginContext>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.mock",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Core",
|
||||
Severity = severity,
|
||||
Diagnosis = diagnosis,
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
mockPlugin.Setup(p => p.GetChecks(It.IsAny<DoctorPluginContext>()))
|
||||
.Returns(new[] { mockCheck.Object });
|
||||
|
||||
return mockPlugin.Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// <copyright file="DoctorReportTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// DoctorReportTests.cs
|
||||
// Sprint: SPRINT_20260112_001_001_DOCTOR_foundation
|
||||
// Task: DOC-FND-008 - Doctor model unit tests
|
||||
// Description: Tests for Doctor report and summary models.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.Models;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DoctorReportTests
|
||||
{
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_WithAllPassed_ReturnsPass()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Pass)
|
||||
};
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Pass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_WithOneFail_ReturnsFail()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Fail),
|
||||
CreateResult(DoctorSeverity.Pass)
|
||||
};
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Fail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_WithOneWarn_NoFail_ReturnsWarn()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Warn),
|
||||
CreateResult(DoctorSeverity.Pass)
|
||||
};
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Warn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_WithOneInfo_NoWarnOrFail_ReturnsInfo()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Info),
|
||||
CreateResult(DoctorSeverity.Pass)
|
||||
};
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Info);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_FailTakesPrecedenceOverWarn()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Warn),
|
||||
CreateResult(DoctorSeverity.Fail)
|
||||
};
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Fail);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeOverallSeverity_WithEmpty_ReturnsPass()
|
||||
{
|
||||
// Arrange
|
||||
var results = Array.Empty<DoctorCheckResult>();
|
||||
|
||||
// Act
|
||||
var severity = DoctorReport.ComputeOverallSeverity(results);
|
||||
|
||||
// Assert
|
||||
severity.Should().Be(DoctorSeverity.Pass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoctorReportSummary_Empty_HasZeroCounts()
|
||||
{
|
||||
// Act
|
||||
var summary = DoctorReportSummary.Empty;
|
||||
|
||||
// Assert
|
||||
summary.Passed.Should().Be(0);
|
||||
summary.Info.Should().Be(0);
|
||||
summary.Warnings.Should().Be(0);
|
||||
summary.Failed.Should().Be(0);
|
||||
summary.Skipped.Should().Be(0);
|
||||
summary.Total.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoctorReportSummary_FromResults_CountsCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var results = new[]
|
||||
{
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Pass),
|
||||
CreateResult(DoctorSeverity.Info),
|
||||
CreateResult(DoctorSeverity.Warn),
|
||||
CreateResult(DoctorSeverity.Fail),
|
||||
CreateResult(DoctorSeverity.Skip)
|
||||
};
|
||||
|
||||
// Act
|
||||
var summary = DoctorReportSummary.FromResults(results);
|
||||
|
||||
// Assert
|
||||
summary.Passed.Should().Be(2);
|
||||
summary.Info.Should().Be(1);
|
||||
summary.Warnings.Should().Be(1);
|
||||
summary.Failed.Should().Be(1);
|
||||
summary.Skipped.Should().Be(1);
|
||||
summary.Total.Should().Be(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DoctorReportSummary_Total_SumsAllCategories()
|
||||
{
|
||||
// Arrange
|
||||
var summary = new DoctorReportSummary
|
||||
{
|
||||
Passed = 5,
|
||||
Info = 2,
|
||||
Warnings = 3,
|
||||
Failed = 1,
|
||||
Skipped = 4
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
summary.Total.Should().Be(15);
|
||||
}
|
||||
|
||||
private static DoctorCheckResult CreateResult(DoctorSeverity severity)
|
||||
{
|
||||
return new DoctorCheckResult
|
||||
{
|
||||
CheckId = $"check.test.{Guid.NewGuid():N}",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = severity,
|
||||
Diagnosis = $"Test result with {severity}",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(10),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// <copyright file="JsonReportFormatterTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JsonReportFormatterTests.cs
|
||||
// Sprint: SPRINT_20260112_001_001_DOCTOR_foundation
|
||||
// Task: DOC-FND-008 - Output formatter unit tests
|
||||
// Description: Tests for the JsonReportFormatter.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Output;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.Output;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class JsonReportFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void FormatName_ReturnsJson()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
|
||||
// Assert
|
||||
formatter.FormatName.Should().Be("json");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithEmptyReport_ReturnsValidJson()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateEmptyReport();
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().NotBeNullOrEmpty();
|
||||
var action = () => JsonDocument.Parse(output);
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsRunId()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateEmptyReport("dr_test_123456");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
doc.RootElement.GetProperty("runId").GetString().Should().Be("dr_test_123456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsSummary()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithMultipleResults();
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var summary = doc.RootElement.GetProperty("summary");
|
||||
summary.GetProperty("passed").GetInt32().Should().Be(2);
|
||||
summary.GetProperty("warnings").GetInt32().Should().Be(1);
|
||||
summary.GetProperty("failed").GetInt32().Should().Be(1);
|
||||
summary.GetProperty("total").GetInt32().Should().Be(4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsResults()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Pass, "Test passed");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var results = doc.RootElement.GetProperty("results");
|
||||
results.GetArrayLength().Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ResultContainsCheckId()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Pass, "Test", "check.test.example");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var results = doc.RootElement.GetProperty("results");
|
||||
results[0].GetProperty("checkId").GetString().Should().Be("check.test.example");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ResultContainsSeverity()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Fail, "Test failed");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var results = doc.RootElement.GetProperty("results");
|
||||
var severityValue = results[0].GetProperty("severity").GetString();
|
||||
severityValue.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ResultContainsDiagnosis()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Pass, "Diagnosis message");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var results = doc.RootElement.GetProperty("results");
|
||||
results[0].GetProperty("diagnosis").GetString().Should().Be("Diagnosis message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsOverallSeverity()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Warn, "Warning");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
doc.RootElement.TryGetProperty("overallSeverity", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsDuration()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateEmptyReport();
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
doc.RootElement.TryGetProperty("duration", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
private static DoctorReport CreateEmptyReport(string? runId = null)
|
||||
{
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = runId ?? $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = DoctorSeverity.Pass,
|
||||
Summary = DoctorReportSummary.Empty,
|
||||
Results = ImmutableArray<DoctorCheckResult>.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithResult(
|
||||
DoctorSeverity severity,
|
||||
string diagnosis,
|
||||
string? checkId = null)
|
||||
{
|
||||
var result = new DoctorCheckResult
|
||||
{
|
||||
CheckId = checkId ?? "check.test.example",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = severity,
|
||||
Diagnosis = diagnosis,
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
var results = ImmutableArray.Create(result);
|
||||
var summary = DoctorReportSummary.FromResults(results);
|
||||
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = severity,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithMultipleResults()
|
||||
{
|
||||
var results = ImmutableArray.Create(
|
||||
CreateCheckResult(DoctorSeverity.Pass, "check.test.1"),
|
||||
CreateCheckResult(DoctorSeverity.Pass, "check.test.2"),
|
||||
CreateCheckResult(DoctorSeverity.Warn, "check.test.3"),
|
||||
CreateCheckResult(DoctorSeverity.Fail, "check.test.4")
|
||||
);
|
||||
|
||||
var summary = DoctorReportSummary.FromResults(results);
|
||||
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = DoctorSeverity.Fail,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorCheckResult CreateCheckResult(DoctorSeverity severity, string checkId)
|
||||
{
|
||||
return new DoctorCheckResult
|
||||
{
|
||||
CheckId = checkId,
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = severity,
|
||||
Diagnosis = $"Result for {checkId}",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// <copyright file="TextReportFormatterTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under AGPL-3.0-or-later.
|
||||
// </copyright>
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TextReportFormatterTests.cs
|
||||
// Sprint: SPRINT_20260112_001_001_DOCTOR_foundation
|
||||
// Task: DOC-FND-008 - Output formatter unit tests
|
||||
// Description: Tests for the TextReportFormatter.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.Doctor.Output;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.Output;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class TextReportFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void FormatName_ReturnsText()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
|
||||
// Assert
|
||||
formatter.FormatName.Should().Be("text");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithEmptyReport_ReturnsValidOutput()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateEmptyReport();
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().NotBeNullOrEmpty();
|
||||
output.Should().Contain("Passed:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithPassedCheck_ContainsPassTag()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Pass, "All good");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("[PASS]");
|
||||
output.Should().Contain("All good");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithFailedCheck_ContainsFailTag()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Fail, "Something failed");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("[FAIL]");
|
||||
output.Should().Contain("Something failed");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithWarningCheck_ContainsWarnTag()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Warn, "Warning message");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("[WARN]");
|
||||
output.Should().Contain("Warning message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsRunId()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateEmptyReport("dr_20260112_123456_abc123");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("dr_20260112_123456_abc123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsSummary()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithMultipleResults();
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("Passed:");
|
||||
output.Should().Contain("Failed:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ContainsCheckId()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithResult(DoctorSeverity.Pass, "Test", "check.test.example");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("check.test.example");
|
||||
}
|
||||
|
||||
private static DoctorReport CreateEmptyReport(string? runId = null)
|
||||
{
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = runId ?? $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = DoctorSeverity.Pass,
|
||||
Summary = DoctorReportSummary.Empty,
|
||||
Results = ImmutableArray<DoctorCheckResult>.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithResult(
|
||||
DoctorSeverity severity,
|
||||
string diagnosis,
|
||||
string? checkId = null)
|
||||
{
|
||||
var result = new DoctorCheckResult
|
||||
{
|
||||
CheckId = checkId ?? "check.test.example",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = severity,
|
||||
Diagnosis = diagnosis,
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
var results = ImmutableArray.Create(result);
|
||||
var summary = DoctorReportSummary.FromResults(results);
|
||||
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = severity,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithMultipleResults()
|
||||
{
|
||||
var results = ImmutableArray.Create(
|
||||
CreateCheckResult(DoctorSeverity.Pass, "check.test.1"),
|
||||
CreateCheckResult(DoctorSeverity.Pass, "check.test.2"),
|
||||
CreateCheckResult(DoctorSeverity.Warn, "check.test.3"),
|
||||
CreateCheckResult(DoctorSeverity.Fail, "check.test.4")
|
||||
);
|
||||
|
||||
var summary = DoctorReportSummary.FromResults(results);
|
||||
|
||||
return new DoctorReport
|
||||
{
|
||||
RunId = $"dr_test_{Guid.NewGuid():N}",
|
||||
StartedAt = DateTimeOffset.UtcNow.AddSeconds(-1),
|
||||
CompletedAt = DateTimeOffset.UtcNow,
|
||||
Duration = TimeSpan.FromSeconds(1),
|
||||
OverallSeverity = DoctorSeverity.Fail,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorCheckResult CreateCheckResult(DoctorSeverity severity, string checkId)
|
||||
{
|
||||
return new DoctorCheckResult
|
||||
{
|
||||
CheckId = checkId,
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = severity,
|
||||
Diagnosis = $"Result for {checkId}",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(50),
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<LangVersion>preview</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3.assert" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
<PackageReference Include="Moq" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../StellaOps.Doctor/StellaOps.Doctor.csproj" />
|
||||
<ProjectReference Include="../../StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user