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