Add channel test providers for Email, Slack, Teams, and Webhook
- Implemented EmailChannelTestProvider to generate email preview payloads. - Implemented SlackChannelTestProvider to create Slack message previews. - Implemented TeamsChannelTestProvider for generating Teams Adaptive Card previews. - Implemented WebhookChannelTestProvider to create webhook payloads. - Added INotifyChannelTestProvider interface for channel-specific preview generation. - Created ChannelTestPreviewContracts for request and response models. - Developed NotifyChannelTestService to handle test send requests and generate previews. - Added rate limit policies for test sends and delivery history. - Implemented unit tests for service registration and binding. - Updated project files to include necessary dependencies and configurations.
This commit is contained in:
		@@ -2,6 +2,7 @@ using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.ObjectModel;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Text;
 | 
			
		||||
@@ -9,6 +10,7 @@ using System.Text.Json;
 | 
			
		||||
using System.Text.Json.Serialization;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Microsoft.IdentityModel.Tokens;
 | 
			
		||||
@@ -21,20 +23,22 @@ using StellaOps.Cli.Services.Models;
 | 
			
		||||
using StellaOps.Cli.Telemetry;
 | 
			
		||||
using StellaOps.Cli.Tests.Testing;
 | 
			
		||||
using StellaOps.Cryptography;
 | 
			
		||||
using Spectre.Console;
 | 
			
		||||
using Spectre.Console.Testing;
 | 
			
		||||
 | 
			
		||||
namespace StellaOps.Cli.Tests.Commands;
 | 
			
		||||
 | 
			
		||||
public sealed class CommandHandlersTests
 | 
			
		||||
{
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExportJobAsync_SetsExitCodeZeroOnSuccess()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "Accepted", "/jobs/export:json/1", null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public sealed class CommandHandlersTests
 | 
			
		||||
{
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExportJobAsync_SetsExitCodeZeroOnSuccess()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "Accepted", "/jobs/export:json/1", null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleExportJobAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                format: "json",
 | 
			
		||||
@@ -45,36 +49,36 @@ public sealed class CommandHandlersTests
 | 
			
		||||
                includeDelta: null,
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("export:json", backend.LastJobKind);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleMergeJobAsync_SetsExitCodeOnFailure()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(false, "Job already running", null, null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleMergeJobAsync(provider, verbose: false, CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("merge:reconcile", backend.LastJobKind);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("export:json", backend.LastJobKind);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleMergeJobAsync_SetsExitCodeOnFailure()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(false, "Job already running", null, null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleMergeJobAsync(provider, verbose: false, CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("merge:reconcile", backend.LastJobKind);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleScannerRunAsync_AutomaticallyUploadsResults()
 | 
			
		||||
    {
 | 
			
		||||
@@ -83,34 +87,34 @@ public sealed class CommandHandlersTests
 | 
			
		||||
        var backend = new StubBackendClient(new JobTriggerResult(true, "Accepted", null, null));
 | 
			
		||||
        var metadataFile = Path.Combine(tempDir.Path, "results", "scan-run.json");
 | 
			
		||||
        var executor = new StubExecutor(new ScannerExecutionResult(0, resultsFile, metadataFile));
 | 
			
		||||
        var options = new StellaOpsCliOptions
 | 
			
		||||
        {
 | 
			
		||||
            ResultsDirectory = Path.Combine(tempDir.Path, "results")
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var provider = BuildServiceProvider(backend, executor, new StubInstaller(), options);
 | 
			
		||||
 | 
			
		||||
        Directory.CreateDirectory(Path.Combine(tempDir.Path, "target"));
 | 
			
		||||
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await CommandHandlers.HandleScannerRunAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                runner: "docker",
 | 
			
		||||
                entry: "scanner-image",
 | 
			
		||||
                targetDirectory: Path.Combine(tempDir.Path, "target"),
 | 
			
		||||
                arguments: Array.Empty<string>(),
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
        var options = new StellaOpsCliOptions
 | 
			
		||||
        {
 | 
			
		||||
            ResultsDirectory = Path.Combine(tempDir.Path, "results")
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        var provider = BuildServiceProvider(backend, executor, new StubInstaller(), options);
 | 
			
		||||
 | 
			
		||||
        Directory.CreateDirectory(Path.Combine(tempDir.Path, "target"));
 | 
			
		||||
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await CommandHandlers.HandleScannerRunAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                runner: "docker",
 | 
			
		||||
                entry: "scanner-image",
 | 
			
		||||
                targetDirectory: Path.Combine(tempDir.Path, "target"),
 | 
			
		||||
                arguments: Array.Empty<string>(),
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal(resultsFile, backend.LastUploadPath);
 | 
			
		||||
            Assert.True(File.Exists(metadataFile));
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -554,7 +558,219 @@ public sealed class CommandHandlersTests
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleRuntimePolicyTestAsync_WritesInteractiveTable()
 | 
			
		||||
    {
 | 
			
		||||
        var originalExit = Environment.ExitCode;
 | 
			
		||||
        var originalConsole = AnsiConsole.Console;
 | 
			
		||||
 | 
			
		||||
        var console = new TestConsole();
 | 
			
		||||
        console.Width(120);
 | 
			
		||||
        console.Interactive();
 | 
			
		||||
        console.EmitAnsiSequences();
 | 
			
		||||
 | 
			
		||||
        AnsiConsole.Console = console;
 | 
			
		||||
 | 
			
		||||
        var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
 | 
			
		||||
 | 
			
		||||
        var decisions = new Dictionary<string, RuntimePolicyImageDecision>(StringComparer.Ordinal)
 | 
			
		||||
        {
 | 
			
		||||
            ["sha256:aaa"] = new RuntimePolicyImageDecision(
 | 
			
		||||
                "allow",
 | 
			
		||||
                true,
 | 
			
		||||
                true,
 | 
			
		||||
                Array.AsReadOnly(new[] { "trusted baseline" }),
 | 
			
		||||
                new RuntimePolicyRekorReference("uuid-allow", "https://rekor.example/entries/uuid-allow", true),
 | 
			
		||||
                new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
                {
 | 
			
		||||
                    ["source"] = "baseline",
 | 
			
		||||
                    ["quieted"] = false,
 | 
			
		||||
                    ["confidence"] = 0.97,
 | 
			
		||||
                    ["confidenceBand"] = "high"
 | 
			
		||||
                })),
 | 
			
		||||
            ["sha256:bbb"] = new RuntimePolicyImageDecision(
 | 
			
		||||
                "block",
 | 
			
		||||
                false,
 | 
			
		||||
                false,
 | 
			
		||||
                Array.AsReadOnly(new[] { "missing attestation" }),
 | 
			
		||||
                new RuntimePolicyRekorReference("uuid-block", "https://rekor.example/entries/uuid-block", false),
 | 
			
		||||
                new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
                {
 | 
			
		||||
                    ["source"] = "policy",
 | 
			
		||||
                    ["quieted"] = false,
 | 
			
		||||
                    ["confidence"] = 0.12,
 | 
			
		||||
                    ["confidenceBand"] = "low"
 | 
			
		||||
                })),
 | 
			
		||||
            ["sha256:ccc"] = new RuntimePolicyImageDecision(
 | 
			
		||||
                "audit",
 | 
			
		||||
                true,
 | 
			
		||||
                false,
 | 
			
		||||
                Array.AsReadOnly(new[] { "pending sbom sync" }),
 | 
			
		||||
                new RuntimePolicyRekorReference(null, null, null),
 | 
			
		||||
                new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
                {
 | 
			
		||||
                    ["source"] = "mirror",
 | 
			
		||||
                    ["quieted"] = true,
 | 
			
		||||
                    ["quietedBy"] = "allow-temporary",
 | 
			
		||||
                    ["confidence"] = 0.42,
 | 
			
		||||
                    ["confidenceBand"] = "medium"
 | 
			
		||||
                }))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        backend.RuntimePolicyResult = new RuntimePolicyEvaluationResult(
 | 
			
		||||
            300,
 | 
			
		||||
            DateTimeOffset.Parse("2025-10-19T12:00:00Z", CultureInfo.InvariantCulture),
 | 
			
		||||
            "rev-42",
 | 
			
		||||
            new ReadOnlyDictionary<string, RuntimePolicyImageDecision>(decisions));
 | 
			
		||||
 | 
			
		||||
        var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await CommandHandlers.HandleRuntimePolicyTestAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                namespaceValue: "prod",
 | 
			
		||||
                imageArguments: new[] { "sha256:aaa", "sha256:bbb" },
 | 
			
		||||
                filePath: null,
 | 
			
		||||
                labelArguments: new[] { "app=frontend" },
 | 
			
		||||
                outputJson: false,
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            var output = console.Output;
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Contains("Image", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("Verdict", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("SBOM Ref", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("Quieted", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("Confidence", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("sha256:aaa", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("uuid-allow", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("(verified)", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("0.97 (high)", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("sha256:bbb", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("uuid-block", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("(unverified)", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("sha256:ccc", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("yes", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.Contains("allow-temporary", output, StringComparison.Ordinal);
 | 
			
		||||
            Assert.True(
 | 
			
		||||
                output.IndexOf("sha256:aaa", StringComparison.Ordinal) <
 | 
			
		||||
                output.IndexOf("sha256:ccc", StringComparison.Ordinal));
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = originalExit;
 | 
			
		||||
            AnsiConsole.Console = originalConsole;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleRuntimePolicyTestAsync_WritesDeterministicJson()
 | 
			
		||||
    {
 | 
			
		||||
        var originalExit = Environment.ExitCode;
 | 
			
		||||
        var originalOut = Console.Out;
 | 
			
		||||
 | 
			
		||||
        var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
 | 
			
		||||
 | 
			
		||||
        var decisions = new Dictionary<string, RuntimePolicyImageDecision>(StringComparer.Ordinal)
 | 
			
		||||
        {
 | 
			
		||||
            ["sha256:json-a"] = new RuntimePolicyImageDecision(
 | 
			
		||||
                "allow",
 | 
			
		||||
                true,
 | 
			
		||||
                true,
 | 
			
		||||
                Array.AsReadOnly(new[] { "baseline allow" }),
 | 
			
		||||
                new RuntimePolicyRekorReference("uuid-json-allow", "https://rekor.example/entries/uuid-json-allow", true),
 | 
			
		||||
                new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
                {
 | 
			
		||||
                    ["source"] = "baseline",
 | 
			
		||||
                    ["confidence"] = 0.66
 | 
			
		||||
                })),
 | 
			
		||||
            ["sha256:json-b"] = new RuntimePolicyImageDecision(
 | 
			
		||||
                "audit",
 | 
			
		||||
                true,
 | 
			
		||||
                false,
 | 
			
		||||
                Array.AsReadOnly(Array.Empty<string>()),
 | 
			
		||||
                new RuntimePolicyRekorReference(null, null, null),
 | 
			
		||||
                new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
 | 
			
		||||
                {
 | 
			
		||||
                    ["source"] = "mirror",
 | 
			
		||||
                    ["quieted"] = true,
 | 
			
		||||
                    ["quietedBy"] = "risk-accepted"
 | 
			
		||||
                }))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        backend.RuntimePolicyResult = new RuntimePolicyEvaluationResult(
 | 
			
		||||
            600,
 | 
			
		||||
            DateTimeOffset.Parse("2025-10-20T00:00:00Z", CultureInfo.InvariantCulture),
 | 
			
		||||
            "rev-json-7",
 | 
			
		||||
            new ReadOnlyDictionary<string, RuntimePolicyImageDecision>(decisions));
 | 
			
		||||
 | 
			
		||||
        var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
        using var writer = new StringWriter();
 | 
			
		||||
        Console.SetOut(writer);
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await CommandHandlers.HandleRuntimePolicyTestAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                namespaceValue: "staging",
 | 
			
		||||
                imageArguments: new[] { "sha256:json-a", "sha256:json-b" },
 | 
			
		||||
                filePath: null,
 | 
			
		||||
                labelArguments: Array.Empty<string>(),
 | 
			
		||||
                outputJson: true,
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            var output = writer.ToString().Trim();
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.False(string.IsNullOrWhiteSpace(output));
 | 
			
		||||
 | 
			
		||||
            using var document = JsonDocument.Parse(output);
 | 
			
		||||
            var root = document.RootElement;
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(600, root.GetProperty("ttlSeconds").GetInt32());
 | 
			
		||||
            Assert.Equal("rev-json-7", root.GetProperty("policyRevision").GetString());
 | 
			
		||||
            var expiresAt = root.GetProperty("expiresAtUtc").GetString();
 | 
			
		||||
            Assert.NotNull(expiresAt);
 | 
			
		||||
            Assert.Equal(
 | 
			
		||||
                DateTimeOffset.Parse("2025-10-20T00:00:00Z", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal),
 | 
			
		||||
                DateTimeOffset.Parse(expiresAt!, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal));
 | 
			
		||||
 | 
			
		||||
            var results = root.GetProperty("results");
 | 
			
		||||
            var keys = results.EnumerateObject().Select(p => p.Name).ToArray();
 | 
			
		||||
            Assert.Equal(new[] { "sha256:json-a", "sha256:json-b" }, keys);
 | 
			
		||||
 | 
			
		||||
            var first = results.GetProperty("sha256:json-a");
 | 
			
		||||
            Assert.Equal("allow", first.GetProperty("policyVerdict").GetString());
 | 
			
		||||
            Assert.True(first.GetProperty("signed").GetBoolean());
 | 
			
		||||
            Assert.True(first.GetProperty("hasSbomReferrers").GetBoolean());
 | 
			
		||||
            var rekor = first.GetProperty("rekor");
 | 
			
		||||
            Assert.Equal("uuid-json-allow", rekor.GetProperty("uuid").GetString());
 | 
			
		||||
            Assert.True(rekor.GetProperty("verified").GetBoolean());
 | 
			
		||||
            Assert.Equal("baseline", first.GetProperty("source").GetString());
 | 
			
		||||
            Assert.Equal(0.66, first.GetProperty("confidence").GetDouble(), 3);
 | 
			
		||||
 | 
			
		||||
            var second = results.GetProperty("sha256:json-b");
 | 
			
		||||
            Assert.Equal("audit", second.GetProperty("policyVerdict").GetString());
 | 
			
		||||
            Assert.True(second.GetProperty("signed").GetBoolean());
 | 
			
		||||
            Assert.False(second.GetProperty("hasSbomReferrers").GetBoolean());
 | 
			
		||||
            Assert.Equal("mirror", second.GetProperty("source").GetString());
 | 
			
		||||
            Assert.True(second.GetProperty("quieted").GetBoolean());
 | 
			
		||||
            Assert.Equal("risk-accepted", second.GetProperty("quietedBy").GetString());
 | 
			
		||||
            Assert.False(second.TryGetProperty("rekor", out _));
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Console.SetOut(originalOut);
 | 
			
		||||
            Environment.ExitCode = originalExit;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static async Task<RevocationArtifactPaths> WriteRevocationArtifactsAsync(TempDirectory temp, string? providerHint)
 | 
			
		||||
    {
 | 
			
		||||
        var (bundleBytes, signature, keyPem) = await BuildRevocationArtifactsAsync(providerHint);
 | 
			
		||||
@@ -665,10 +881,17 @@ public sealed class CommandHandlersTests
 | 
			
		||||
            $"{Path.GetFileNameWithoutExtension(tempResultsFile)}-run.json");
 | 
			
		||||
        return new StubExecutor(new ScannerExecutionResult(0, tempResultsFile, tempMetadataFile));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private sealed class StubBackendClient : IBackendOperationsClient
 | 
			
		||||
    {
 | 
			
		||||
        private readonly JobTriggerResult _jobResult;
 | 
			
		||||
        private static readonly RuntimePolicyEvaluationResult DefaultRuntimePolicyResult =
 | 
			
		||||
            new RuntimePolicyEvaluationResult(
 | 
			
		||||
                0,
 | 
			
		||||
                null,
 | 
			
		||||
                null,
 | 
			
		||||
                new ReadOnlyDictionary<string, RuntimePolicyImageDecision>(
 | 
			
		||||
                    new Dictionary<string, RuntimePolicyImageDecision>()));
 | 
			
		||||
 | 
			
		||||
        public StubBackendClient(JobTriggerResult result)
 | 
			
		||||
        {
 | 
			
		||||
@@ -683,6 +906,7 @@ public sealed class CommandHandlersTests
 | 
			
		||||
        public List<(string ExportId, string DestinationPath, string? Algorithm, string? Digest)> ExportDownloads { get; } = new();
 | 
			
		||||
        public ExcititorOperationResult? ExcititorResult { get; set; } = new ExcititorOperationResult(true, "ok", null, null);
 | 
			
		||||
        public IReadOnlyList<ExcititorProviderSummary> ProviderSummaries { get; set; } = Array.Empty<ExcititorProviderSummary>();
 | 
			
		||||
        public RuntimePolicyEvaluationResult RuntimePolicyResult { get; set; } = DefaultRuntimePolicyResult;
 | 
			
		||||
 | 
			
		||||
        public Task<ScannerArtifactResult> DownloadScannerAsync(string channel, string outputPath, bool overwrite, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
            => throw new NotImplementedException();
 | 
			
		||||
@@ -726,21 +950,18 @@ public sealed class CommandHandlersTests
 | 
			
		||||
            => Task.FromResult(ProviderSummaries);
 | 
			
		||||
 | 
			
		||||
        public Task<RuntimePolicyEvaluationResult> EvaluateRuntimePolicyAsync(RuntimePolicyEvaluationRequest request, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var empty = new ReadOnlyDictionary<string, RuntimePolicyImageDecision>(new Dictionary<string, RuntimePolicyImageDecision>());
 | 
			
		||||
            return Task.FromResult(new RuntimePolicyEvaluationResult(0, null, null, empty));
 | 
			
		||||
        }
 | 
			
		||||
            => Task.FromResult(RuntimePolicyResult);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubExecutor : IScannerExecutor
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ScannerExecutionResult _result;
 | 
			
		||||
 | 
			
		||||
        public StubExecutor(ScannerExecutionResult result)
 | 
			
		||||
        {
 | 
			
		||||
            _result = result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private sealed class StubExecutor : IScannerExecutor
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ScannerExecutionResult _result;
 | 
			
		||||
 | 
			
		||||
        public StubExecutor(ScannerExecutionResult result)
 | 
			
		||||
        {
 | 
			
		||||
            _result = result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<ScannerExecutionResult> RunAsync(string runner, string entry, string targetDirectory, string resultsDirectory, IReadOnlyList<string> arguments, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            Directory.CreateDirectory(Path.GetDirectoryName(_result.ResultsPath)!);
 | 
			
		||||
@@ -757,8 +978,8 @@ public sealed class CommandHandlersTests
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(_result);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubInstaller : IScannerInstaller
 | 
			
		||||
    {
 | 
			
		||||
        public Task InstallAsync(string artifactPath, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user