Initial commit (history squashed)
This commit is contained in:
		
							
								
								
									
										533
									
								
								src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										533
									
								
								src/StellaOps.Cli.Tests/Commands/CommandHandlersTests.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,533 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Text.Json;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using Microsoft.Extensions.DependencyInjection;
 | 
			
		||||
using Microsoft.Extensions.Logging;
 | 
			
		||||
using Microsoft.IdentityModel.Tokens;
 | 
			
		||||
using StellaOps.Auth.Abstractions;
 | 
			
		||||
using StellaOps.Auth.Client;
 | 
			
		||||
using StellaOps.Cli.Commands;
 | 
			
		||||
using StellaOps.Cli.Configuration;
 | 
			
		||||
using StellaOps.Cli.Services;
 | 
			
		||||
using StellaOps.Cli.Services.Models;
 | 
			
		||||
using StellaOps.Cli.Telemetry;
 | 
			
		||||
using StellaOps.Cli.Tests.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);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleExportJobAsync(
 | 
			
		||||
                provider,
 | 
			
		||||
                format: "json",
 | 
			
		||||
                delta: false,
 | 
			
		||||
                publishFull: null,
 | 
			
		||||
                publishDelta: null,
 | 
			
		||||
                includeFull: null,
 | 
			
		||||
                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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleScannerRunAsync_AutomaticallyUploadsResults()
 | 
			
		||||
    {
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
        var resultsFile = Path.Combine(tempDir.Path, "results", "scan.json");
 | 
			
		||||
        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);
 | 
			
		||||
            Assert.Equal(resultsFile, backend.LastUploadPath);
 | 
			
		||||
            Assert.True(File.Exists(metadataFile));
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthLoginAsync_UsesClientCredentialsFlow()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    ClientSecret = "secret",
 | 
			
		||||
                    Scope = "feedser.jobs.trigger",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var tokenClient = new StubTokenClient();
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: tokenClient);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthLoginAsync(provider, options, verbose: false, force: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
            Assert.Equal(1, tokenClient.ClientCredentialRequests);
 | 
			
		||||
            Assert.NotNull(tokenClient.CachedEntry);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthLoginAsync_FailsWhenPasswordMissing()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    Username = "user",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: new StubTokenClient());
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthLoginAsync(provider, options, verbose: false, force: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthStatusAsync_ReportsMissingToken()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: new StubTokenClient());
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthStatusAsync(provider, options, verbose: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthStatusAsync_ReportsCachedToken()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var tokenClient = new StubTokenClient();
 | 
			
		||||
            tokenClient.CachedEntry = new StellaOpsTokenCacheEntry(
 | 
			
		||||
                "token",
 | 
			
		||||
                "Bearer",
 | 
			
		||||
                DateTimeOffset.UtcNow.AddMinutes(30),
 | 
			
		||||
                new[] { StellaOpsScopes.FeedserJobsTrigger });
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: tokenClient);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthStatusAsync(provider, options, verbose: true, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthWhoAmIAsync_ReturnsErrorWhenTokenMissing()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: new StubTokenClient());
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthWhoAmIAsync(provider, options, verbose: false, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(1, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthWhoAmIAsync_ReportsClaimsForJwtToken()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var tokenClient = new StubTokenClient();
 | 
			
		||||
            tokenClient.CachedEntry = new StellaOpsTokenCacheEntry(
 | 
			
		||||
                CreateUnsignedJwt(
 | 
			
		||||
                    ("sub", "cli-user"),
 | 
			
		||||
                    ("aud", "feedser"),
 | 
			
		||||
                    ("iss", "https://authority.example"),
 | 
			
		||||
                    ("iat", 1_700_000_000),
 | 
			
		||||
                    ("nbf", 1_700_000_000)),
 | 
			
		||||
                "Bearer",
 | 
			
		||||
                DateTimeOffset.UtcNow.AddMinutes(30),
 | 
			
		||||
                new[] { StellaOpsScopes.FeedserJobsTrigger });
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: tokenClient);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthWhoAmIAsync(provider, options, verbose: true, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Fact]
 | 
			
		||||
    public async Task HandleAuthLogoutAsync_ClearsToken()
 | 
			
		||||
    {
 | 
			
		||||
        var original = Environment.ExitCode;
 | 
			
		||||
        using var tempDir = new TempDirectory();
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var options = new StellaOpsCliOptions
 | 
			
		||||
            {
 | 
			
		||||
                ResultsDirectory = Path.Combine(tempDir.Path, "results"),
 | 
			
		||||
                Authority = new StellaOpsCliAuthorityOptions
 | 
			
		||||
                {
 | 
			
		||||
                    Url = "https://authority.example",
 | 
			
		||||
                    ClientId = "cli",
 | 
			
		||||
                    TokenCacheDirectory = tempDir.Path
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var tokenClient = new StubTokenClient();
 | 
			
		||||
            tokenClient.CachedEntry = new StellaOpsTokenCacheEntry(
 | 
			
		||||
                "token",
 | 
			
		||||
                "Bearer",
 | 
			
		||||
                DateTimeOffset.UtcNow.AddMinutes(5),
 | 
			
		||||
                new[] { StellaOpsScopes.FeedserJobsTrigger });
 | 
			
		||||
 | 
			
		||||
            var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)), options: options, tokenClient: tokenClient);
 | 
			
		||||
 | 
			
		||||
            await CommandHandlers.HandleAuthLogoutAsync(provider, options, verbose: true, cancellationToken: CancellationToken.None);
 | 
			
		||||
 | 
			
		||||
            Assert.Null(tokenClient.CachedEntry);
 | 
			
		||||
            Assert.Equal(1, tokenClient.ClearRequests);
 | 
			
		||||
            Assert.Equal(0, Environment.ExitCode);
 | 
			
		||||
        }
 | 
			
		||||
        finally
 | 
			
		||||
        {
 | 
			
		||||
            Environment.ExitCode = original;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IServiceProvider BuildServiceProvider(
 | 
			
		||||
        IBackendOperationsClient backend,
 | 
			
		||||
        IScannerExecutor? executor = null,
 | 
			
		||||
        IScannerInstaller? installer = null,
 | 
			
		||||
        StellaOpsCliOptions? options = null,
 | 
			
		||||
        IStellaOpsTokenClient? tokenClient = null)
 | 
			
		||||
    {
 | 
			
		||||
        var services = new ServiceCollection();
 | 
			
		||||
        services.AddSingleton(backend);
 | 
			
		||||
        services.AddSingleton<ILoggerFactory>(_ => LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug)));
 | 
			
		||||
        services.AddSingleton(new VerbosityState());
 | 
			
		||||
        var resolvedOptions = options ?? new StellaOpsCliOptions
 | 
			
		||||
        {
 | 
			
		||||
            ResultsDirectory = Path.Combine(Path.GetTempPath(), $"stellaops-cli-results-{Guid.NewGuid():N}")
 | 
			
		||||
        };
 | 
			
		||||
        services.AddSingleton(resolvedOptions);
 | 
			
		||||
 | 
			
		||||
        var resolvedExecutor = executor ?? CreateDefaultExecutor();
 | 
			
		||||
        services.AddSingleton<IScannerExecutor>(resolvedExecutor);
 | 
			
		||||
        services.AddSingleton<IScannerInstaller>(installer ?? new StubInstaller());
 | 
			
		||||
 | 
			
		||||
        if (tokenClient is not null)
 | 
			
		||||
        {
 | 
			
		||||
            services.AddSingleton(tokenClient);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return services.BuildServiceProvider();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static IScannerExecutor CreateDefaultExecutor()
 | 
			
		||||
    {
 | 
			
		||||
        var tempResultsFile = Path.GetTempFileName();
 | 
			
		||||
        var tempMetadataFile = Path.Combine(
 | 
			
		||||
            Path.GetDirectoryName(tempResultsFile)!,
 | 
			
		||||
            $"{Path.GetFileNameWithoutExtension(tempResultsFile)}-run.json");
 | 
			
		||||
        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 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)!);
 | 
			
		||||
            if (!File.Exists(_result.ResultsPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.WriteAllText(_result.ResultsPath, "{}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Directory.CreateDirectory(Path.GetDirectoryName(_result.RunMetadataPath)!);
 | 
			
		||||
            if (!File.Exists(_result.RunMetadataPath))
 | 
			
		||||
            {
 | 
			
		||||
                File.WriteAllText(_result.RunMetadataPath, "{}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return Task.FromResult(_result);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubInstaller : IScannerInstaller
 | 
			
		||||
    {
 | 
			
		||||
        public Task InstallAsync(string artifactPath, bool verbose, CancellationToken cancellationToken)
 | 
			
		||||
            => Task.CompletedTask;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private sealed class StubTokenClient : IStellaOpsTokenClient
 | 
			
		||||
    {
 | 
			
		||||
        private readonly StellaOpsTokenResult _token;
 | 
			
		||||
 | 
			
		||||
        public StubTokenClient()
 | 
			
		||||
        {
 | 
			
		||||
            _token = new StellaOpsTokenResult(
 | 
			
		||||
                "token-123",
 | 
			
		||||
                "Bearer",
 | 
			
		||||
                DateTimeOffset.UtcNow.AddMinutes(30),
 | 
			
		||||
                new[] { StellaOpsScopes.FeedserJobsTrigger });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public int ClientCredentialRequests { get; private set; }
 | 
			
		||||
        public int PasswordRequests { get; private set; }
 | 
			
		||||
        public int ClearRequests { get; private set; }
 | 
			
		||||
        public StellaOpsTokenCacheEntry? CachedEntry { get; set; }
 | 
			
		||||
 | 
			
		||||
        public ValueTask CacheTokenAsync(string key, StellaOpsTokenCacheEntry entry, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            CachedEntry = entry;
 | 
			
		||||
            return ValueTask.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public ValueTask ClearCachedTokenAsync(string key, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            ClearRequests++;
 | 
			
		||||
            CachedEntry = null;
 | 
			
		||||
            return ValueTask.CompletedTask;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<JsonWebKeySet> GetJsonWebKeySetAsync(CancellationToken cancellationToken = default)
 | 
			
		||||
            => Task.FromResult(new JsonWebKeySet("{\"keys\":[]}"));
 | 
			
		||||
 | 
			
		||||
        public ValueTask<StellaOpsTokenCacheEntry?> GetCachedTokenAsync(string key, CancellationToken cancellationToken = default)
 | 
			
		||||
            => ValueTask.FromResult(CachedEntry);
 | 
			
		||||
 | 
			
		||||
        public Task<StellaOpsTokenResult> RequestClientCredentialsTokenAsync(string? scope = null, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            ClientCredentialRequests++;
 | 
			
		||||
            return Task.FromResult(_token);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<StellaOpsTokenResult> RequestPasswordTokenAsync(string username, string password, string? scope = null, CancellationToken cancellationToken = default)
 | 
			
		||||
        {
 | 
			
		||||
            PasswordRequests++;
 | 
			
		||||
            return Task.FromResult(_token);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static string CreateUnsignedJwt(params (string Key, object Value)[] claims)
 | 
			
		||||
    {
 | 
			
		||||
        var headerJson = "{\"alg\":\"none\",\"typ\":\"JWT\"}";
 | 
			
		||||
        var payload = new Dictionary<string, object?>(StringComparer.Ordinal);
 | 
			
		||||
        foreach (var claim in claims)
 | 
			
		||||
        {
 | 
			
		||||
            payload[claim.Key] = claim.Value;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var payloadJson = JsonSerializer.Serialize(payload);
 | 
			
		||||
        return $"{Base64UrlEncode(headerJson)}.{Base64UrlEncode(payloadJson)}.";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static string Base64UrlEncode(string value)
 | 
			
		||||
    {
 | 
			
		||||
        var bytes = Encoding.UTF8.GetBytes(value);
 | 
			
		||||
        return Convert.ToBase64String(bytes)
 | 
			
		||||
            .TrimEnd('=')
 | 
			
		||||
            .Replace('+', '-')
 | 
			
		||||
            .Replace('/', '_');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user