FUll implementation plan (first draft)
This commit is contained in:
		@@ -1,6 +1,7 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Net.Http;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
@@ -211,6 +212,113 @@ public sealed class CommandHandlersTests
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExcititorInitAsync_CallsBackend()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "accepted", null, null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleExcititorInitAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                new[] { "redhat" },
 | 
			
		||||
                resume: true,
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("init", backend.LastExcititorRoute);
 | 
			
		||||
            Assert.Equal(HttpMethod.Post, backend.LastExcititorMethod);
 | 
			
		||||
            var payload = Assert.IsAssignableFrom<IDictionary<string, object?>>(backend.LastExcititorPayload);
 | 
			
		||||
            Assert.Equal(true, payload["resume"]);
 | 
			
		||||
            var providers = Assert.IsAssignableFrom<IEnumerable<string>>(payload["providers"]!);
 | 
			
		||||
            Assert.Contains("redhat", providers, StringComparer.OrdinalIgnoreCase);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExcititorListProvidersAsync_WritesOutput()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
 | 
			
		||||
            {
 | 
			
		||||
                ProviderSummaries = new[]
 | 
			
		||||
                {
 | 
			
		||||
                    new ExcititorProviderSummary("redhat", "distro", "Red Hat", "vendor", true, DateTimeOffset.UtcNow)
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
            await CommandHandlers.HandleExcititorListProvidersAsync(provider, includeDisabled: false, verbose: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExcititorVerifyAsync_FailsWithoutArguments()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleExcititorVerifyAsync(provider, null, null, null, verbose: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleExcititorVerifyAsync_AttachesAttestationFile()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempFile = new TempFile("attestation.json", Encoding.UTF8.GetBytes("{\"ok\":true}"));
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
 | 
			
		||||
            var provider = BuildServiceProvider(backend);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleExcititorVerifyAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                exportId: "export-123",
 | 
			
		||||
                digest: "sha256:abc",
 | 
			
		||||
                attestationPath: tempFile.Path,
 | 
			
		||||
                verbose: false,
 | 
			
		||||
                cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal("verify", backend.LastExcititorRoute);
 | 
			
		||||
            var payload = Assert.IsAssignableFrom<IDictionary<string, object?>>(backend.LastExcititorPayload);
 | 
			
		||||
            Assert.Equal("export-123", payload["exportId"]);
 | 
			
		||||
            Assert.Equal("sha256:abc", payload["digest"]);
 | 
			
		||||
            var attestation = Assert.IsAssignableFrom<IDictionary<string, object?>>(payload["attestation"]!);
 | 
			
		||||
            Assert.Equal(Path.GetFileName(tempFile.Path), attestation["fileName"]);
 | 
			
		||||
            Assert.NotNull(attestation["base64"]);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Theory]
 | 
			
		||||
    [InlineData(null)]
 | 
			
		||||
    [InlineData("default")]
 | 
			
		||||
@@ -502,33 +610,49 @@ public sealed class CommandHandlersTests
 | 
			
		||||
        return new StubExecutor(new ScannerExecutionResult(0, tempResultsFile, tempMetadataFile));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubBackendClient : IBackendOperationsClient
 | 
			
		||||
    {
 | 
			
		||||
        private readonly JobTriggerResult _result;
 | 
			
		||||
 | 
			
		||||
        public StubBackendClient(JobTriggerResult result)
 | 
			
		||||
        {
 | 
			
		||||
            _result = result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string? LastJobKind { get; private set; }
 | 
			
		||||
        public string? LastUploadPath { get; private set; }
 | 
			
		||||
 | 
			
		||||
        public Task<ScannerArtifactResult> DownloadScannerAsync(string channel, string outputPath, bool overwrite, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
            => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
        public Task UploadScanResultsAsync(string filePath, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            LastUploadPath = filePath;
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<JobTriggerResult> TriggerJobAsync(string jobKind, IDictionary<string, object?> parameters, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            LastJobKind = jobKind;
 | 
			
		||||
            return Task.FromResult(_result);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    private sealed class StubBackendClient : IBackendOperationsClient
 | 
			
		||||
    {
 | 
			
		||||
        private readonly JobTriggerResult _jobResult;
 | 
			
		||||
 | 
			
		||||
        public StubBackendClient(JobTriggerResult result)
 | 
			
		||||
        {
 | 
			
		||||
            _jobResult = result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public string? LastJobKind { get; private set; }
 | 
			
		||||
        public string? LastUploadPath { get; private set; }
 | 
			
		||||
        public string? LastExcititorRoute { get; private set; }
 | 
			
		||||
        public HttpMethod? LastExcititorMethod { get; private set; }
 | 
			
		||||
        public object? LastExcititorPayload { get; private set; }
 | 
			
		||||
        public ExcititorOperationResult? ExcititorResult { get; set; } = new ExcititorOperationResult(true, "ok", null, null);
 | 
			
		||||
        public IReadOnlyList<ExcititorProviderSummary> ProviderSummaries { get; set; } = Array.Empty<ExcititorProviderSummary>();
 | 
			
		||||
 | 
			
		||||
        public Task<ScannerArtifactResult> DownloadScannerAsync(string channel, string outputPath, bool overwrite, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
            => throw new NotImplementedException();
 | 
			
		||||
 | 
			
		||||
        public Task UploadScanResultsAsync(string filePath, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            LastUploadPath = filePath;
 | 
			
		||||
            return Task.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<JobTriggerResult> TriggerJobAsync(string jobKind, IDictionary<string, object?> parameters, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            LastJobKind = jobKind;
 | 
			
		||||
            return Task.FromResult(_jobResult);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<ExcititorOperationResult> ExecuteExcititorOperationAsync(string route, HttpMethod method, object? payload, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            LastExcititorRoute = route;
 | 
			
		||||
            LastExcititorMethod = method;
 | 
			
		||||
            LastExcititorPayload = payload;
 | 
			
		||||
            return Task.FromResult(ExcititorResult ?? new ExcititorOperationResult(true, "ok", null, null));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<IReadOnlyList<ExcititorProviderSummary>> GetExcititorProvidersAsync(bool includeDisabled, CancellationToken cancellationToken)
 | 
			
		||||
            => Task.FromResult(ProviderSummaries);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubExecutor : IScannerExecutor
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user