save checkpoint: save features
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Doctor.AdvisoryAI;
|
||||
using StellaOps.Doctor.Models;
|
||||
using StellaOps.TestKit.Templates;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.AdvisoryAI;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DoctorContextAdapterTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateContextAsync_ProjectsSummaryAndEnrichedEvidence()
|
||||
{
|
||||
var registry = new InMemoryEvidenceSchemaRegistry();
|
||||
registry.RegisterCommonSchemas();
|
||||
var timeProvider = new FlakyToDeterministicPattern.FakeTimeProvider(
|
||||
new DateTimeOffset(2026, 2, 11, 18, 0, 0, TimeSpan.Zero));
|
||||
var adapter = new DoctorContextAdapter(
|
||||
registry,
|
||||
NullLogger<DoctorContextAdapter>.Instance,
|
||||
timeProvider);
|
||||
|
||||
var report = new DoctorReport
|
||||
{
|
||||
RunId = "dr_context_001",
|
||||
StartedAt = new DateTimeOffset(2026, 2, 11, 17, 59, 0, TimeSpan.Zero),
|
||||
CompletedAt = new DateTimeOffset(2026, 2, 11, 18, 0, 0, TimeSpan.Zero),
|
||||
Duration = TimeSpan.FromMinutes(1),
|
||||
OverallSeverity = DoctorSeverity.Fail,
|
||||
Summary = new DoctorReportSummary
|
||||
{
|
||||
Passed = 0,
|
||||
Info = 0,
|
||||
Warnings = 0,
|
||||
Failed = 1,
|
||||
Skipped = 0
|
||||
},
|
||||
Results =
|
||||
[
|
||||
new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.integration.latency",
|
||||
PluginId = "stellaops.doctor.integration",
|
||||
Category = "Integration",
|
||||
Severity = DoctorSeverity.Fail,
|
||||
Diagnosis = "Latency is too high.",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Connection probe metrics",
|
||||
Data = new Dictionary<string, string>
|
||||
{
|
||||
["connection_latency_ms"] = "1450",
|
||||
["endpoint"] = "http://localhost:9000"
|
||||
}
|
||||
},
|
||||
LikelyCauses = ["network_issue"],
|
||||
Remediation = new Remediation
|
||||
{
|
||||
RequiresBackup = false,
|
||||
SafetyNote = "No backup required.",
|
||||
RunbookUrl = "https://docs.stella-ops.local/runbooks/network-latency",
|
||||
Steps =
|
||||
[
|
||||
new RemediationStep
|
||||
{
|
||||
Order = 1,
|
||||
Description = "Inspect endpoint latency",
|
||||
Command = "stella doctor --check check.integration.latency",
|
||||
CommandType = CommandType.Api
|
||||
}
|
||||
]
|
||||
},
|
||||
VerificationCommand = "stella doctor --check check.integration.latency",
|
||||
Duration = TimeSpan.FromMilliseconds(200),
|
||||
ExecutedAt = new DateTimeOffset(2026, 2, 11, 17, 59, 30, TimeSpan.Zero)
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var context = await adapter.CreateContextAsync(report, CancellationToken.None);
|
||||
|
||||
context.RunId.Should().Be("dr_context_001");
|
||||
context.Summary.FailedChecks.Should().Be(1);
|
||||
context.Summary.CategoriesWithIssues.Should().ContainSingle("Integration");
|
||||
context.Results.Should().HaveCount(1);
|
||||
context.Results[0].Evidence.Fields.Should().ContainKey("connection_latency_ms");
|
||||
context.Results[0].Evidence.Fields["connection_latency_ms"].ExpectedRange.Should().Be("< 1000");
|
||||
context.Results[0].Remediation.Should().NotBeNull();
|
||||
context.Results[0].Remediation!.RunbookUrl.Should().Be("https://docs.stella-ops.local/runbooks/network-latency");
|
||||
context.PlatformContext.Should().ContainKey("captured_at_utc");
|
||||
context.PlatformContext.Should().ContainKey("doctor_version");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Doctor.AdvisoryAI;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Doctor.Tests.AdvisoryAI;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class InMemoryEvidenceSchemaRegistryTests
|
||||
{
|
||||
[Fact]
|
||||
public void RegisterCommonSchemas_ExposesWildcardSchemaLookup()
|
||||
{
|
||||
var registry = new InMemoryEvidenceSchemaRegistry();
|
||||
|
||||
registry.RegisterCommonSchemas();
|
||||
|
||||
var schema = registry.GetFieldSchema("check.integration.example", "connection_latency_ms");
|
||||
schema.Should().NotBeNull();
|
||||
schema!.ExpectedRange.Should().Be("< 1000");
|
||||
schema.IsKeyDiagnostic.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisterSchema_OverridesWildcardForSpecificCheck()
|
||||
{
|
||||
var registry = new InMemoryEvidenceSchemaRegistry();
|
||||
registry.RegisterCommonSchemas();
|
||||
registry.RegisterSchema("check.specific", "connection_latency_ms", new EvidenceFieldSchema
|
||||
{
|
||||
ExpectedRange = "< 100",
|
||||
Description = "Strict latency threshold."
|
||||
});
|
||||
|
||||
var schema = registry.GetFieldSchema("check.specific", "connection_latency_ms");
|
||||
|
||||
schema.Should().NotBeNull();
|
||||
schema!.ExpectedRange.Should().Be("< 100");
|
||||
schema.Description.Should().Be("Strict latency threshold.");
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,26 @@ public sealed class JsonReportFormatterTests
|
||||
doc.RootElement.TryGetProperty("duration", out _).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithRunbookUrlInRemediation_ContainsRunbookUrl()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new JsonReportFormatter();
|
||||
var report = CreateReportWithRemediationRunbook("https://docs.stella-ops.org/runbooks/test-check");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
using var doc = JsonDocument.Parse(output);
|
||||
|
||||
// Assert
|
||||
var runbookUrl = doc.RootElement
|
||||
.GetProperty("results")[0]
|
||||
.GetProperty("remediation")
|
||||
.GetProperty("runbookUrl")
|
||||
.GetString();
|
||||
runbookUrl.Should().Be("https://docs.stella-ops.org/runbooks/test-check");
|
||||
}
|
||||
|
||||
private static DoctorReport CreateEmptyReport(string? runId = null)
|
||||
{
|
||||
return new DoctorReport
|
||||
@@ -249,6 +269,55 @@ public sealed class JsonReportFormatterTests
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithRemediationRunbook(string runbookUrl)
|
||||
{
|
||||
var remediation = new Remediation
|
||||
{
|
||||
RunbookUrl = runbookUrl,
|
||||
Steps =
|
||||
[
|
||||
new RemediationStep
|
||||
{
|
||||
Order = 1,
|
||||
Description = "Apply deterministic fix",
|
||||
Command = "stella doctor fix --from report.json",
|
||||
CommandType = CommandType.Shell
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var result = new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.remediation.runbook",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = DoctorSeverity.Fail,
|
||||
Diagnosis = "Runbook projection test",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Remediation = remediation,
|
||||
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 = DoctorSeverity.Fail,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorCheckResult CreateCheckResult(DoctorSeverity severity, string checkId)
|
||||
{
|
||||
return new DoctorCheckResult
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// <copyright file="MarkdownReportFormatterTests.cs" company="Stella Operations">
|
||||
// Copyright (c) Stella Operations. Licensed under BUSL-1.1.
|
||||
// </copyright>
|
||||
|
||||
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 MarkdownReportFormatterTests
|
||||
{
|
||||
[Fact]
|
||||
public void Format_WithRunbookUrlInRemediation_ContainsClickableRunbookLink()
|
||||
{
|
||||
var formatter = new MarkdownReportFormatter();
|
||||
var report = CreateReportWithRemediationRunbook("https://docs.stella-ops.org/runbooks/markdown-check");
|
||||
|
||||
var output = formatter.Format(report);
|
||||
|
||||
output.Should().Contain("**Runbook:** [https://docs.stella-ops.org/runbooks/markdown-check](https://docs.stella-ops.org/runbooks/markdown-check)");
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithRemediationRunbook(string runbookUrl)
|
||||
{
|
||||
var result = new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.markdown.runbook",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = DoctorSeverity.Fail,
|
||||
Diagnosis = "Runbook markdown output test",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Remediation = new Remediation
|
||||
{
|
||||
RunbookUrl = runbookUrl,
|
||||
Steps =
|
||||
[
|
||||
new RemediationStep
|
||||
{
|
||||
Order = 1,
|
||||
Description = "Apply fix",
|
||||
Command = "stella doctor fix --from report.json",
|
||||
CommandType = CommandType.Shell
|
||||
}
|
||||
]
|
||||
},
|
||||
Duration = TimeSpan.FromMilliseconds(10),
|
||||
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 = DoctorSeverity.Fail,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,21 @@ public sealed class TextReportFormatterTests
|
||||
output.Should().Contain("check.test.example");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_WithRunbookUrlInRemediation_ContainsRunbookLink()
|
||||
{
|
||||
// Arrange
|
||||
var formatter = new TextReportFormatter();
|
||||
var report = CreateReportWithRemediationRunbook("https://docs.stella-ops.org/runbooks/text-check");
|
||||
|
||||
// Act
|
||||
var output = formatter.Format(report);
|
||||
|
||||
// Assert
|
||||
output.Should().Contain("Runbook:");
|
||||
output.Should().Contain("https://docs.stella-ops.org/runbooks/text-check");
|
||||
}
|
||||
|
||||
private static DoctorReport CreateEmptyReport(string? runId = null)
|
||||
{
|
||||
return new DoctorReport
|
||||
@@ -224,4 +239,51 @@ public sealed class TextReportFormatterTests
|
||||
ExecutedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private static DoctorReport CreateReportWithRemediationRunbook(string runbookUrl)
|
||||
{
|
||||
var result = new DoctorCheckResult
|
||||
{
|
||||
CheckId = "check.test.runbook.output",
|
||||
PluginId = "test.plugin",
|
||||
Category = "Test",
|
||||
Severity = DoctorSeverity.Fail,
|
||||
Diagnosis = "Runbook output test",
|
||||
Evidence = new Evidence
|
||||
{
|
||||
Description = "Test evidence",
|
||||
Data = new Dictionary<string, string>()
|
||||
},
|
||||
Remediation = new Remediation
|
||||
{
|
||||
RunbookUrl = runbookUrl,
|
||||
Steps =
|
||||
[
|
||||
new RemediationStep
|
||||
{
|
||||
Order = 1,
|
||||
Description = "Apply fix",
|
||||
Command = "stella doctor fix --from report.json",
|
||||
CommandType = CommandType.Shell
|
||||
}
|
||||
]
|
||||
},
|
||||
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 = DoctorSeverity.Fail,
|
||||
Summary = summary,
|
||||
Results = results
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,5 +4,7 @@ Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_sol
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| DOC-RMD-B1-001 | DONE | Added runbook URL projection coverage in JSON/Text/Markdown formatter tests for `doctor-runbook-url-integration` run-002 remediation. |
|
||||
| DOC-RMD-B1-003 | DONE | Added AdvisoryAI unit coverage (`DoctorContextAdapterTests`, `InMemoryEvidenceSchemaRegistryTests`) for run-002 diagnosis remediation in shared Doctor library. |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/__Libraries/__Tests/StellaOps.Doctor.Tests/StellaOps.Doctor.Tests.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
|
||||
Reference in New Issue
Block a user