partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
namespace StellaOps.Cli.Extensions
|
||||
{
|
||||
// Isolated test project stub namespace for UnknownsCommandGroup compile.
|
||||
internal static class CompatStubs
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace StellaOps.Policy.Unknowns.Models
|
||||
{
|
||||
// Isolated test project stubs to satisfy UnknownsCommandGroup compile.
|
||||
public sealed record UnknownPlaceholder;
|
||||
}
|
||||
|
||||
namespace System.CommandLine
|
||||
{
|
||||
// Compatibility shims for the System.CommandLine API shape expected by UnknownsCommandGroup.
|
||||
public static class OptionCompatExtensions
|
||||
{
|
||||
public static Option<T> SetDefaultValue<T>(this Option<T> option, T value)
|
||||
{
|
||||
return option;
|
||||
}
|
||||
|
||||
public static Option<T> FromAmong<T>(this Option<T> option, params T[] values)
|
||||
{
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
<RootNamespace>StellaOps.Cli.UnknownsExport.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\StellaOps.Cli\Commands\UnknownsCommandGroup.cs" Link="Commands\UnknownsCommandGroup.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,141 @@
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Cli.Commands;
|
||||
|
||||
namespace StellaOps.Cli.UnknownsExport.Tests;
|
||||
|
||||
public sealed class UnknownsExportIsolationTests
|
||||
{
|
||||
[Fact]
|
||||
public void UnknownsExport_ParsesSchemaVersionAndFormat()
|
||||
{
|
||||
var services = BuildServices([]);
|
||||
var command = UnknownsCommandGroup.BuildUnknownsCommand(
|
||||
services,
|
||||
new Option<bool>("--verbose"),
|
||||
CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
|
||||
var result = root.Parse("unknowns export --format json --schema-version unknowns.export.v9");
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownsExport_DefaultFormat_Parses()
|
||||
{
|
||||
var services = BuildServices([]);
|
||||
var command = UnknownsCommandGroup.BuildUnknownsCommand(
|
||||
services,
|
||||
new Option<bool>("--verbose"),
|
||||
CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
|
||||
var result = root.Parse("unknowns export");
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnknownsExport_Json_IncludesSchemaEnvelopeAndMetadata()
|
||||
{
|
||||
var payload = """
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "11111111-1111-1111-1111-111111111111",
|
||||
"packageId": "pkg:npm/a@1.0.0",
|
||||
"packageVersion": "1.0.0",
|
||||
"band": "hot",
|
||||
"score": 0.99,
|
||||
"reasonCode": "r1",
|
||||
"reasonCodeShort": "r1",
|
||||
"firstSeenAt": "2026-01-01T00:00:00Z",
|
||||
"lastEvaluatedAt": "2026-01-15T09:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": "22222222-2222-2222-2222-222222222222",
|
||||
"packageId": "pkg:npm/b@1.0.0",
|
||||
"packageVersion": "1.0.0",
|
||||
"band": "warm",
|
||||
"score": 0.55,
|
||||
"reasonCode": "r2",
|
||||
"reasonCodeShort": "r2",
|
||||
"firstSeenAt": "2026-01-01T00:00:00Z",
|
||||
"lastEvaluatedAt": "2026-01-14T09:30:00Z"
|
||||
}
|
||||
],
|
||||
"totalCount": 2
|
||||
}
|
||||
""";
|
||||
|
||||
var services = BuildServices([("/api/v1/policy/unknowns?limit=10000", payload)]);
|
||||
var command = UnknownsCommandGroup.BuildUnknownsCommand(
|
||||
services,
|
||||
new Option<bool>("--verbose"),
|
||||
CancellationToken.None);
|
||||
var root = new RootCommand { command };
|
||||
|
||||
var writer = new StringWriter();
|
||||
var originalOut = Console.Out;
|
||||
int exitCode;
|
||||
try
|
||||
{
|
||||
Console.SetOut(writer);
|
||||
exitCode = await root.Parse("unknowns export --format json --schema-version unknowns.export.v2").InvokeAsync();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Console.SetOut(originalOut);
|
||||
}
|
||||
|
||||
var output = writer.ToString();
|
||||
Assert.True(exitCode == 0, $"ExitCode={exitCode}; Output={output}");
|
||||
Assert.Contains("\"schemaVersion\": \"unknowns.export.v2\"", output, StringComparison.Ordinal);
|
||||
Assert.Contains("\"itemCount\": 2", output, StringComparison.Ordinal);
|
||||
Assert.Contains("\"exportedAt\": \"2026-01-15T09:30:00+00:00\"", output, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static ServiceProvider BuildServices(IReadOnlyList<(string Path, string Json)> payloads)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging(static builder => builder.SetMinimumLevel(LogLevel.Warning));
|
||||
services.AddHttpClient("PolicyApi")
|
||||
.ConfigureHttpClient(static client => client.BaseAddress = new Uri("http://localhost"))
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new TestHandler(payloads));
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
private sealed class TestHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly Dictionary<string, string> _responses;
|
||||
|
||||
public TestHandler(IReadOnlyList<(string Path, string Json)> payloads)
|
||||
{
|
||||
_responses = payloads.ToDictionary(
|
||||
static x => x.Path,
|
||||
static x => x.Json,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
var key = request.RequestUri?.PathAndQuery ?? string.Empty;
|
||||
if (_responses.TryGetValue(key, out var json))
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
});
|
||||
}
|
||||
|
||||
return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
Content = new StringContent("""{"error":"not found"}""", Encoding.UTF8, "application/json")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user