wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10
This commit is contained in:
@@ -7,7 +7,9 @@ using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using StellaOps.Cli.Commands;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.Cli.Services;
|
||||
using StellaOps.Cli.Services.Models;
|
||||
using StellaOps.Cli.Services.Models.AdvisoryAi;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
@@ -150,6 +152,113 @@ public sealed class KnowledgeSearchCommandGroupTests
|
||||
backend.VerifyAll();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdvisoryAiSourcesPrepareCommand_GeneratesSeedArtifacts()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
var docsDirectory = Path.Combine(temp.Path, "docs", "runbooks");
|
||||
Directory.CreateDirectory(docsDirectory);
|
||||
var markdownPath = Path.Combine(docsDirectory, "network.md");
|
||||
await File.WriteAllTextAsync(markdownPath, "# Network\n## Retry\nUse retries.");
|
||||
|
||||
var docsAllowListPath = Path.Combine(temp.Path, "knowledge-docs-allowlist.json");
|
||||
await File.WriteAllTextAsync(
|
||||
docsAllowListPath,
|
||||
"""
|
||||
{
|
||||
"include": [
|
||||
"docs"
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
var doctorSeedPath = Path.Combine(temp.Path, "doctor-search-seed.json");
|
||||
await File.WriteAllTextAsync(
|
||||
doctorSeedPath,
|
||||
"""
|
||||
[
|
||||
{
|
||||
"checkCode": "check.core.db.connectivity",
|
||||
"title": "PostgreSQL connectivity",
|
||||
"severity": "high",
|
||||
"description": "Connectivity issue.",
|
||||
"remediation": "Fix database connection settings.",
|
||||
"runCommand": "stella doctor run --check check.core.db.connectivity",
|
||||
"symptoms": ["connection refused"],
|
||||
"tags": ["doctor"],
|
||||
"references": ["docs/INSTALL_GUIDE.md"]
|
||||
}
|
||||
]
|
||||
""");
|
||||
|
||||
var docsManifestPath = Path.Combine(temp.Path, "knowledge-docs-manifest.json");
|
||||
var openApiOutputPath = Path.Combine(temp.Path, "openapi.aggregate.json");
|
||||
var doctorControlsPath = Path.Combine(temp.Path, "doctor-search-controls.json");
|
||||
|
||||
ApiSpecDownloadRequest? capturedApiRequest = null;
|
||||
var backend = new Mock<IBackendOperationsClient>(MockBehavior.Strict);
|
||||
backend
|
||||
.Setup(client => client.DownloadApiSpecAsync(
|
||||
It.IsAny<ApiSpecDownloadRequest>(),
|
||||
It.IsAny<CancellationToken>()))
|
||||
.Callback<ApiSpecDownloadRequest, CancellationToken>((request, _) => capturedApiRequest = request)
|
||||
.ReturnsAsync(new ApiSpecDownloadResult
|
||||
{
|
||||
Success = true,
|
||||
Path = openApiOutputPath,
|
||||
FromCache = false,
|
||||
Checksum = "deadbeef",
|
||||
ChecksumAlgorithm = "sha256"
|
||||
});
|
||||
|
||||
using var services = new ServiceCollection()
|
||||
.AddSingleton(backend.Object)
|
||||
.BuildServiceProvider();
|
||||
|
||||
var root = new RootCommand();
|
||||
root.Add(KnowledgeSearchCommandGroup.BuildAdvisoryAiCommand(
|
||||
services,
|
||||
new Option<bool>("--verbose"),
|
||||
CancellationToken.None));
|
||||
|
||||
var invocation = await InvokeWithCapturedConsoleAsync(
|
||||
root,
|
||||
$"advisoryai sources prepare --repo-root \"{temp.Path}\" --docs-allowlist \"{docsAllowListPath}\" --docs-manifest-output \"{docsManifestPath}\" --openapi-output \"{openApiOutputPath}\" --doctor-seed \"{doctorSeedPath}\" --doctor-controls-output \"{doctorControlsPath}\" --json");
|
||||
|
||||
Assert.Equal(0, invocation.ExitCode);
|
||||
Assert.NotNull(capturedApiRequest);
|
||||
Assert.Equal(openApiOutputPath, capturedApiRequest!.OutputPath);
|
||||
Assert.Equal("openapi-json", capturedApiRequest.Format);
|
||||
Assert.Null(capturedApiRequest.Service);
|
||||
Assert.True(File.Exists(docsManifestPath));
|
||||
Assert.True(File.Exists(doctorControlsPath));
|
||||
|
||||
using var manifest = JsonDocument.Parse(await File.ReadAllTextAsync(docsManifestPath));
|
||||
var include = manifest.RootElement.GetProperty("include");
|
||||
Assert.True(include.GetArrayLength() >= 1);
|
||||
Assert.Contains(include.EnumerateArray(), element => element.GetString() == "docs");
|
||||
var documents = manifest.RootElement.GetProperty("documents");
|
||||
Assert.Equal(1, documents.GetArrayLength());
|
||||
Assert.Equal("docs/runbooks/network.md", documents[0].GetProperty("path").GetString());
|
||||
|
||||
using var controls = JsonDocument.Parse(await File.ReadAllTextAsync(doctorControlsPath));
|
||||
Assert.Equal(1, controls.RootElement.GetArrayLength());
|
||||
Assert.Equal("check.core.db.connectivity", controls.RootElement[0].GetProperty("checkCode").GetString());
|
||||
Assert.Equal("manual", controls.RootElement[0].GetProperty("control").GetString());
|
||||
Assert.True(controls.RootElement[0].GetProperty("requiresConfirmation").GetBoolean());
|
||||
Assert.Equal("PostgreSQL connectivity", controls.RootElement[0].GetProperty("title").GetString());
|
||||
Assert.Equal("high", controls.RootElement[0].GetProperty("severity").GetString());
|
||||
Assert.Equal("stella doctor run --check check.core.db.connectivity", controls.RootElement[0].GetProperty("runCommand").GetString());
|
||||
Assert.Contains(
|
||||
controls.RootElement[0].GetProperty("tags").EnumerateArray(),
|
||||
static tag => string.Equals(tag.GetString(), "doctor", StringComparison.Ordinal));
|
||||
Assert.Contains(
|
||||
controls.RootElement[0].GetProperty("references").EnumerateArray(),
|
||||
static reference => string.Equals(reference.GetString(), "docs/INSTALL_GUIDE.md", StringComparison.Ordinal));
|
||||
|
||||
backend.VerifyAll();
|
||||
}
|
||||
|
||||
private static AdvisoryKnowledgeSearchResponseModel CreateSearchResponse()
|
||||
{
|
||||
return new AdvisoryKnowledgeSearchResponseModel
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StellaOps.Cli.Services;
|
||||
using StellaOps.Infrastructure.Postgres.Migrations;
|
||||
using StellaOps.Platform.Database;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class MigrationCommandServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void GetMissingLegacyMigrations_WhenConsolidatedOnlyApplied_ReturnsAllLegacyMigrations()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Scanner");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(module!);
|
||||
var applied = new[]
|
||||
{
|
||||
new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum)
|
||||
};
|
||||
|
||||
var missing = MigrationCommandService.GetMissingLegacyMigrations(artifact, applied);
|
||||
|
||||
Assert.Equal(artifact.SourceMigrations.Count, missing.Count);
|
||||
Assert.Equal(
|
||||
artifact.SourceMigrations.Select(static migration => migration.Name),
|
||||
missing.Select(static migration => migration.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetMissingLegacyMigrations_WhenPartiallyBackfilled_ReturnsOnlyMissingMigrations()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Scanner");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(module!);
|
||||
var backfilled = artifact.SourceMigrations.Take(2).ToArray();
|
||||
var applied = backfilled
|
||||
.Select(static migration => new MigrationInfo(migration.Name, DateTimeOffset.UtcNow, migration.Checksum))
|
||||
.Concat(new[] { new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum) })
|
||||
.ToArray();
|
||||
|
||||
var missing = MigrationCommandService.GetMissingLegacyMigrations(artifact, applied);
|
||||
|
||||
Assert.Equal(artifact.SourceMigrations.Count - backfilled.Length, missing.Count);
|
||||
Assert.DoesNotContain(missing, migration => backfilled.Any(existing => existing.Name == migration.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsConsolidatedInSync_WhenChecksumsMatch_ReturnsTrue()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Platform");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(module!);
|
||||
var applied = new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, artifact.Checksum);
|
||||
|
||||
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, applied);
|
||||
|
||||
Assert.True(inSync);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsConsolidatedInSync_WhenChecksumsDiffer_ReturnsFalse()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Platform");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(module!);
|
||||
var applied = new MigrationInfo(artifact.MigrationName, DateTimeOffset.UtcNow, "deadbeef");
|
||||
|
||||
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, applied);
|
||||
|
||||
Assert.False(inSync);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsConsolidatedInSync_WhenConsolidatedNotApplied_ReturnsFalse()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Platform");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(module!);
|
||||
|
||||
var inSync = MigrationCommandService.IsConsolidatedInSync(artifact, null);
|
||||
|
||||
Assert.False(inSync);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StellaOps.Platform.Database;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
public sealed class MigrationModuleConsolidationTests
|
||||
{
|
||||
[Fact]
|
||||
public void Build_ForEveryRegisteredModule_ProducesOneUniqueConsolidatedMigration()
|
||||
{
|
||||
var modules = MigrationModuleRegistry.GetModules(null).ToArray();
|
||||
var migrationNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var module in modules)
|
||||
{
|
||||
var artifact = MigrationModuleConsolidation.Build(module);
|
||||
Assert.NotNull(artifact);
|
||||
Assert.NotEmpty(artifact.Script);
|
||||
Assert.NotEmpty(artifact.Checksum);
|
||||
Assert.NotEmpty(artifact.SourceMigrations);
|
||||
Assert.True(
|
||||
migrationNames.Add(artifact.MigrationName),
|
||||
$"Duplicate consolidated migration name '{artifact.MigrationName}' for module '{module.Name}'.");
|
||||
}
|
||||
|
||||
Assert.Equal(modules.Length, migrationNames.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_ForScanner_ProducesSingleConsolidatedMigration()
|
||||
{
|
||||
var scanner = MigrationModuleRegistry.FindModule("Scanner");
|
||||
Assert.NotNull(scanner);
|
||||
|
||||
var artifact = MigrationModuleConsolidation.Build(scanner!);
|
||||
Assert.Equal("100_consolidated_scanner.sql", artifact.MigrationName);
|
||||
Assert.Equal(36, artifact.SourceMigrations.Count);
|
||||
Assert.Contains(
|
||||
artifact.SourceMigrations,
|
||||
static migration => string.Equals(
|
||||
migration.Name,
|
||||
"022a_runtime_observations_compat.sql",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Contains(
|
||||
artifact.SourceMigrations,
|
||||
static migration => string.Equals(
|
||||
migration.Name,
|
||||
"V3700_001__triage_schema.sql",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_IsDeterministic_ForSameModule()
|
||||
{
|
||||
var module = MigrationModuleRegistry.FindModule("Platform");
|
||||
Assert.NotNull(module);
|
||||
|
||||
var first = MigrationModuleConsolidation.Build(module!);
|
||||
var second = MigrationModuleConsolidation.Build(module!);
|
||||
|
||||
Assert.Equal(first.MigrationName, second.MigrationName);
|
||||
Assert.Equal(first.Checksum, second.Checksum);
|
||||
Assert.Equal(first.Script, second.Script);
|
||||
Assert.Equal(first.SourceMigrations.Select(static migration => migration.Name), second.SourceMigrations.Select(static migration => migration.Name));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using StellaOps.Platform.Database;
|
||||
using Xunit;
|
||||
@@ -10,18 +11,35 @@ public class MigrationModuleRegistryTests
|
||||
public void Modules_Populated_With_All_Postgres_Modules()
|
||||
{
|
||||
var modules = MigrationModuleRegistry.Modules;
|
||||
Assert.Equal(10, modules.Count);
|
||||
Assert.True(modules.Count >= 20, $"Expected at least 20 registered modules, found {modules.Count}");
|
||||
Assert.Contains(modules, m => m.Name == "AdvisoryAI" && m.SchemaName == "advisoryai");
|
||||
Assert.Contains(modules, m => m.Name == "AirGap" && m.SchemaName == "airgap");
|
||||
Assert.Contains(modules, m => m.Name == "Authority" && m.SchemaName == "authority");
|
||||
Assert.Contains(modules, m => m.Name == "Eventing" && m.SchemaName == "timeline");
|
||||
Assert.Contains(modules, m => m.Name == "Evidence" && m.SchemaName == "evidence");
|
||||
Assert.Contains(modules, m => m.Name == "Scheduler" && m.SchemaName == "scheduler");
|
||||
Assert.Contains(modules, m => m.Name == "Concelier" && m.SchemaName == "vuln");
|
||||
Assert.Contains(modules, m => m.Name == "Policy" && m.SchemaName == "policy");
|
||||
Assert.Contains(modules, m => m.Name == "Notify" && m.SchemaName == "notify");
|
||||
Assert.Contains(modules, m => m.Name == "Excititor" && m.SchemaName == "vex");
|
||||
Assert.Contains(modules, m => m.Name == "PluginRegistry" && m.SchemaName == "platform");
|
||||
Assert.Contains(modules, m => m.Name == "Platform" && m.SchemaName == "release");
|
||||
Assert.Contains(modules, m => m.Name == "Scanner" && m.SchemaName == "scanner");
|
||||
var scanner = Assert.Single(modules, static module => module.Name == "Scanner" && module.SchemaName == "scanner");
|
||||
Assert.Equal(2, scanner.Sources.Count);
|
||||
Assert.Contains(
|
||||
scanner.Sources,
|
||||
static source => string.Equals(
|
||||
source.ResourcePrefix,
|
||||
"StellaOps.Scanner.Triage.Migrations",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Contains(modules, m => m.Name == "TimelineIndexer" && m.SchemaName == "timeline");
|
||||
Assert.Equal(10, MigrationModuleRegistry.ModuleNames.Count());
|
||||
Assert.Contains(modules, m => m.Name == "VexHub" && m.SchemaName == "vexhub");
|
||||
Assert.Contains(modules, m => m.Name == "Remediation" && m.SchemaName == "remediation");
|
||||
Assert.Contains(modules, m => m.Name == "VexLens" && m.SchemaName == "vexlens");
|
||||
Assert.Contains(modules, m => m.Name == "SbomLineage" && m.SchemaName == "sbom");
|
||||
Assert.Contains(modules, m => m.Name == "ReachGraph" && m.SchemaName == "reachgraph");
|
||||
Assert.Contains(modules, m => m.Name == "Verdict" && m.SchemaName == "stellaops");
|
||||
Assert.True(MigrationModuleRegistry.ModuleNames.Count() >= 20);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,6 +78,6 @@ public class MigrationModuleRegistryTests
|
||||
public void GetModules_All_Returns_All()
|
||||
{
|
||||
var result = MigrationModuleRegistry.GetModules(null);
|
||||
Assert.Equal(10, result.Count());
|
||||
Assert.True(result.Count() >= 20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System.CommandLine;
|
||||
using System.Net;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Moq.Protected;
|
||||
using StellaOps.Cli.Commands.Budget;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Commands;
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
public sealed class RiskBudgetCommandTenantHeaderTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task BudgetStatus_AddsTenantHeader_FromTenantOption()
|
||||
{
|
||||
// Arrange
|
||||
var (services, handlerMock) = CreateServices();
|
||||
HttpRequestMessage? capturedRequest = null;
|
||||
handlerMock
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.Is<HttpRequestMessage>(request =>
|
||||
request.Method == HttpMethod.Get &&
|
||||
request.RequestUri != null &&
|
||||
request.RequestUri.ToString().Contains("/api/v1/policy/risk-budget/status/", StringComparison.Ordinal)),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||
.ReturnsAsync(CreateStatusResponse());
|
||||
|
||||
var command = RiskBudgetCommandGroup.BuildBudgetCommand(services, new Option<bool>("--verbose"), CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
using var writer = new StringWriter();
|
||||
var originalOut = Console.Out;
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse("budget status --service svc-a --output json --tenant Tenant-Bravo").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
|
||||
Assert.Equal("tenant-bravo", Assert.Single(tenantValues));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BudgetStatus_AddsTenantHeader_FromEnvironmentFallback()
|
||||
{
|
||||
// Arrange
|
||||
var originalTenant = Environment.GetEnvironmentVariable("STELLAOPS_TENANT");
|
||||
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", "Tenant-Env");
|
||||
|
||||
var (services, handlerMock) = CreateServices();
|
||||
HttpRequestMessage? capturedRequest = null;
|
||||
handlerMock
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.Is<HttpRequestMessage>(request =>
|
||||
request.Method == HttpMethod.Get &&
|
||||
request.RequestUri != null &&
|
||||
request.RequestUri.ToString().Contains("/api/v1/policy/risk-budget/status/", StringComparison.Ordinal)),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||
.ReturnsAsync(CreateStatusResponse());
|
||||
|
||||
var command = RiskBudgetCommandGroup.BuildBudgetCommand(services, new Option<bool>("--verbose"), CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
using var writer = new StringWriter();
|
||||
var originalOut = Console.Out;
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse("budget status --service svc-a --output json").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", originalTenant);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
|
||||
Assert.Equal("tenant-env", Assert.Single(tenantValues));
|
||||
}
|
||||
|
||||
private static (IServiceProvider Services, Mock<HttpMessageHandler> HandlerMock) CreateServices()
|
||||
{
|
||||
var handlerMock = new Mock<HttpMessageHandler>();
|
||||
var httpClient = new HttpClient(handlerMock.Object)
|
||||
{
|
||||
BaseAddress = new Uri("http://localhost:8080"),
|
||||
};
|
||||
|
||||
var factoryMock = new Mock<IHttpClientFactory>();
|
||||
factoryMock
|
||||
.Setup(factory => factory.CreateClient("PolicyApi"))
|
||||
.Returns(httpClient);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton(factoryMock.Object);
|
||||
services.AddSingleton(NullLoggerFactory.Instance);
|
||||
return (services.BuildServiceProvider(), handlerMock);
|
||||
}
|
||||
|
||||
private static HttpResponseMessage CreateStatusResponse()
|
||||
{
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(
|
||||
"""
|
||||
{
|
||||
"serviceId": "svc-a",
|
||||
"window": "2026-02",
|
||||
"tier": 1,
|
||||
"allocated": 100,
|
||||
"consumed": 15,
|
||||
"remaining": 85,
|
||||
"percentageUsed": 15.0,
|
||||
"status": "green"
|
||||
}
|
||||
""")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -372,6 +372,94 @@ public class UnknownsGreyQueueCommandTests
|
||||
Assert.Contains("id,package_id,package_version,band,score", output);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownsList_AddsTenantHeader_FromTenantOption()
|
||||
{
|
||||
// Arrange
|
||||
HttpRequestMessage? capturedRequest = null;
|
||||
_httpHandlerMock
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.Is<HttpRequestMessage>(request =>
|
||||
request.Method == HttpMethod.Get &&
|
||||
request.RequestUri != null &&
|
||||
request.RequestUri.ToString().Contains("/api/v1/policy/unknowns", StringComparison.Ordinal)),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent("""{ "items": [], "totalCount": 0 }""")
|
||||
});
|
||||
|
||||
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, new Option<bool>("--verbose"), CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
using var writer = new StringWriter();
|
||||
var originalOut = Console.Out;
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse("unknowns --tenant Tenant-Bravo list --format json").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
|
||||
Assert.Equal("tenant-bravo", Assert.Single(tenantValues));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownsList_AddsTenantHeader_FromEnvironmentFallback()
|
||||
{
|
||||
// Arrange
|
||||
var originalTenant = Environment.GetEnvironmentVariable("STELLAOPS_TENANT");
|
||||
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", "Tenant-Env");
|
||||
|
||||
HttpRequestMessage? capturedRequest = null;
|
||||
_httpHandlerMock
|
||||
.Protected()
|
||||
.Setup<Task<HttpResponseMessage>>(
|
||||
"SendAsync",
|
||||
ItExpr.Is<HttpRequestMessage>(request =>
|
||||
request.Method == HttpMethod.Get &&
|
||||
request.RequestUri != null &&
|
||||
request.RequestUri.ToString().Contains("/api/v1/policy/unknowns", StringComparison.Ordinal)),
|
||||
ItExpr.IsAny<CancellationToken>())
|
||||
.Callback<HttpRequestMessage, CancellationToken>((request, _) => capturedRequest = request)
|
||||
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent("""{ "items": [], "totalCount": 0 }""")
|
||||
});
|
||||
|
||||
var command = UnknownsCommandGroup.BuildUnknownsCommand(_services, new Option<bool>("--verbose"), CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
using var writer = new StringWriter();
|
||||
var originalOut = Console.Out;
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse("unknowns list --format json").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
Environment.SetEnvironmentVariable("STELLAOPS_TENANT", originalTenant);
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, exitCode);
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.True(capturedRequest.Headers.TryGetValues("X-Tenant-Id", out var tenantValues));
|
||||
Assert.Equal("tenant-env", Assert.Single(tenantValues));
|
||||
}
|
||||
|
||||
private void SetupPolicyUnknownsResponse(string json)
|
||||
{
|
||||
_httpHandlerMock
|
||||
|
||||
@@ -5,7 +5,9 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| SPRINT_20260222_051-AKS-CLI-TESTS | DONE | Added AKS CLI source-preparation command coverage (`AdvisoryAiSourcesPrepareCommand_GeneratesSeedArtifacts`) including enriched doctor control metadata assertions, and revalidated knowledge-search command group tests (4/4 on 2026-02-22). |
|
||||
| SPRINT_20260222_051-MGC-04-W1-TESTS | DONE | Updated migration registry/system command tests for platform-owned 10-module coverage and validated with `dotnet test` (1182 passed on 2026-02-22). |
|
||||
| SPRINT_20260222_051-MGC-04-W1-SOURCES-TESTS | DONE | Extended migration tests to assert per-service source-set flattening metadata, deterministic synthesized consolidated artifact generation (including unique consolidated artifact per registered plugin), and partial-backfill missing-legacy detection behavior; revalidated with `dotnet test` (`1194` passed on 2026-02-22). |
|
||||
| AUDIT-0143-M | DONE | Revalidated 2026-01-06. |
|
||||
| AUDIT-0143-T | DONE | Revalidated 2026-01-06. |
|
||||
| AUDIT-0143-A | DONE | Waived (test project; revalidated 2026-01-06). |
|
||||
|
||||
@@ -12,6 +12,14 @@ namespace StellaOps.Policy.Unknowns.Models
|
||||
public sealed record UnknownPlaceholder;
|
||||
}
|
||||
|
||||
namespace StellaOps.Cli.Services
|
||||
{
|
||||
internal static class TenantProfileStore
|
||||
{
|
||||
public static string? GetEffectiveTenant(string? commandLineTenant) => commandLineTenant;
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.CommandLine
|
||||
{
|
||||
// Compatibility shims for the System.CommandLine API shape expected by UnknownsCommandGroup.
|
||||
|
||||
Reference in New Issue
Block a user