using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Spectre.Console; using Spectre.Console.Testing; using StellaOps.Cli.Commands; using StellaOps.Cli.Configuration; using StellaOps.Cli.Services; using StellaOps.Cli.Services.Models; namespace StellaOps.Cli.Tests.Commands; public sealed class VerifyImageHandlerTests { [Fact] public void ParseImageReference_WithDigest_Parses() { var (registry, repository, digest) = CommandHandlers.ParseImageReference("gcr.io/myproject/myapp@sha256:abc123"); Assert.Equal("gcr.io", registry); Assert.Equal("myproject/myapp", repository); Assert.Equal("sha256:abc123", digest); } [Fact] public async Task HandleVerifyImageAsync_ValidResult_ReturnsZero() { var result = new ImageVerificationResult { ImageReference = "registry.example.com/app@sha256:deadbeef", ImageDigest = "sha256:deadbeef", VerifiedAt = DateTimeOffset.UtcNow, IsValid = true }; var provider = BuildServices(new StubVerifier(result)); var originalExit = Environment.ExitCode; try { await CaptureConsoleAsync(async _ => { var exitCode = await CommandHandlers.HandleVerifyImageAsync( provider, "registry.example.com/app@sha256:deadbeef", new[] { "sbom" }, trustPolicy: null, output: "json", strict: false, verbose: false, cancellationToken: CancellationToken.None); Assert.Equal(0, exitCode); }); Assert.Equal(0, Environment.ExitCode); } finally { Environment.ExitCode = originalExit; } } [Fact] public async Task HandleVerifyImageAsync_InvalidResult_ReturnsOne() { var result = new ImageVerificationResult { ImageReference = "registry.example.com/app@sha256:deadbeef", ImageDigest = "sha256:deadbeef", VerifiedAt = DateTimeOffset.UtcNow, IsValid = false }; var provider = BuildServices(new StubVerifier(result)); var originalExit = Environment.ExitCode; try { await CaptureConsoleAsync(async _ => { var exitCode = await CommandHandlers.HandleVerifyImageAsync( provider, "registry.example.com/app@sha256:deadbeef", new[] { "sbom" }, trustPolicy: null, output: "json", strict: true, verbose: false, cancellationToken: CancellationToken.None); Assert.Equal(1, exitCode); }); Assert.Equal(1, Environment.ExitCode); } finally { Environment.ExitCode = originalExit; } } private static ServiceProvider BuildServices(IImageAttestationVerifier verifier) { var services = new ServiceCollection(); services.AddLogging(builder => builder.SetMinimumLevel(LogLevel.None)); services.AddSingleton(new StellaOpsCliOptions()); services.AddSingleton(verifier); return services.BuildServiceProvider(); } private static async Task CaptureConsoleAsync(Func action) { var testConsole = new TestConsole(); var originalConsole = AnsiConsole.Console; var originalOut = Console.Out; using var writer = new StringWriter(); try { AnsiConsole.Console = testConsole; Console.SetOut(writer); await action(testConsole).ConfigureAwait(false); } finally { AnsiConsole.Console = originalConsole; Console.SetOut(originalOut); } } private sealed class StubVerifier : IImageAttestationVerifier { private readonly ImageVerificationResult _result; public StubVerifier(ImageVerificationResult result) { _result = result; } public Task VerifyAsync(ImageVerificationRequest request, CancellationToken cancellationToken = default) => Task.FromResult(_result); } }