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 | ||||
|     { | ||||
|   | ||||
| @@ -1,14 +1,15 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Net.Http; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Net.Http; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using System.Text; | ||||
|  | ||||
| namespace StellaOps.Cli.Tests.Testing; | ||||
|  | ||||
| internal sealed class TempDirectory : IDisposable | ||||
| { | ||||
| internal sealed class TempDirectory : IDisposable | ||||
| { | ||||
|     public TempDirectory() | ||||
|     { | ||||
|         Path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"stellaops-cli-tests-{Guid.NewGuid():N}"); | ||||
| @@ -31,7 +32,41 @@ internal sealed class TempDirectory : IDisposable | ||||
|             // ignored | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|  | ||||
| internal sealed class TempFile : IDisposable | ||||
| { | ||||
|     public TempFile(string fileName, byte[] contents) | ||||
|     { | ||||
|         var directory = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"stellaops-cli-file-{Guid.NewGuid():N}"); | ||||
|         Directory.CreateDirectory(directory); | ||||
|         Path = System.IO.Path.Combine(directory, fileName); | ||||
|         File.WriteAllBytes(Path, contents); | ||||
|     } | ||||
|  | ||||
|     public string Path { get; } | ||||
|  | ||||
|     public void Dispose() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (File.Exists(Path)) | ||||
|             { | ||||
|                 File.Delete(Path); | ||||
|             } | ||||
|  | ||||
|             var directory = System.IO.Path.GetDirectoryName(Path); | ||||
|             if (!string.IsNullOrEmpty(directory) && Directory.Exists(directory)) | ||||
|             { | ||||
|                 Directory.Delete(directory, recursive: true); | ||||
|             } | ||||
|         } | ||||
|         catch | ||||
|         { | ||||
|             // ignored intentionally | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| internal sealed class StubHttpMessageHandler : HttpMessageHandler | ||||
| { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user