Refactor code structure for improved readability and maintainability
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-06 21:48:12 +02:00
parent f6c22854a4
commit dd0067ea0b
105 changed files with 12662 additions and 427 deletions

View File

@@ -23,4 +23,17 @@ public sealed class CommandFactoryTests
Assert.Contains(ruby.Subcommands, command => string.Equals(command.Name, "inspect", StringComparison.Ordinal));
Assert.Contains(ruby.Subcommands, command => string.Equals(command.Name, "resolve", StringComparison.Ordinal));
}
[Fact]
public void Create_ExposesBunInspectAndResolveCommands()
{
using var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.None));
var services = new ServiceCollection().BuildServiceProvider();
var root = CommandFactory.Create(services, new StellaOpsCliOptions(), CancellationToken.None, loggerFactory);
var bun = Assert.Single(root.Subcommands, command => string.Equals(command.Name, "bun", StringComparison.Ordinal));
Assert.Contains(bun.Subcommands, command => string.Equals(command.Name, "inspect", StringComparison.Ordinal));
Assert.Contains(bun.Subcommands, command => string.Equals(command.Name, "resolve", StringComparison.Ordinal));
}
}

View File

@@ -25,6 +25,7 @@ using StellaOps.Cli.Configuration;
using StellaOps.Cli.Services;
using StellaOps.Cli.Services.Models;
using StellaOps.Cli.Services.Models.AdvisoryAi;
using StellaOps.Cli.Services.Models.Bun;
using StellaOps.Cli.Services.Models.Ruby;
using StellaOps.Cli.Telemetry;
using StellaOps.Cli.Tests.Testing;
@@ -641,6 +642,161 @@ public sealed class CommandHandlersTests
}
}
[Fact]
public async Task HandleBunInspectAsync_WritesJson()
{
var originalExit = Environment.ExitCode;
using var fixture = new TempDirectory();
CreateBunWorkspace(fixture.Path);
var provider = BuildServiceProvider(new StubBackendClient(new JobTriggerResult(true, "ok", null, null)));
try
{
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleBunInspectAsync(
provider,
fixture.Path,
"json",
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(0, Environment.ExitCode);
using var document = JsonDocument.Parse(output.PlainBuffer);
var packages = document.RootElement.GetProperty("packages");
Assert.NotEmpty(packages.EnumerateArray());
Assert.Contains(packages.EnumerateArray(), p =>
string.Equals(p.GetProperty("name").GetString(), "lodash", StringComparison.OrdinalIgnoreCase));
Assert.Contains(packages.EnumerateArray(), p =>
string.Equals(p.GetProperty("name").GetString(), "express", StringComparison.OrdinalIgnoreCase));
}
finally
{
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleBunResolveAsync_RendersPackages()
{
var originalExit = Environment.ExitCode;
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
{
BunInventory = CreateBunInventory(
"scan-bun",
new[]
{
CreateBunPackageItem("lodash", "4.17.21", isDev: false, isDirect: true),
CreateBunPackageItem("express", "4.18.2", isDev: false, isDirect: true),
CreateBunPackageItem("typescript", "5.3.3", isDev: true, isDirect: true)
})
};
var provider = BuildServiceProvider(backend);
try
{
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleBunResolveAsync(
provider,
imageReference: null,
scanId: "scan-bun",
format: "table",
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(0, Environment.ExitCode);
Assert.Equal("scan-bun", backend.LastBunPackagesScanId);
Assert.Contains("scan-bun", output.Combined, StringComparison.OrdinalIgnoreCase);
Assert.Contains("lodash", output.Combined, StringComparison.OrdinalIgnoreCase);
Assert.Contains("express", output.Combined, StringComparison.OrdinalIgnoreCase);
}
finally
{
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleBunResolveAsync_WritesJson()
{
var originalExit = Environment.ExitCode;
const string identifier = "bun-scan-json";
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null))
{
BunInventory = CreateBunInventory(
identifier,
new[]
{
CreateBunPackageItem("lodash", "4.17.21", isDev: false, isDirect: true)
})
};
var provider = BuildServiceProvider(backend);
try
{
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleBunResolveAsync(
provider,
imageReference: identifier,
scanId: null,
format: "json",
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(0, Environment.ExitCode);
Assert.Equal(identifier, backend.LastBunPackagesScanId);
using var document = JsonDocument.Parse(output.PlainBuffer);
Assert.Equal(identifier, document.RootElement.GetProperty("scanId").GetString());
var packages = document.RootElement.GetProperty("packages");
Assert.Single(packages.EnumerateArray());
var package = packages.EnumerateArray().First();
Assert.Equal("lodash", package.GetProperty("name").GetString());
Assert.Equal("4.17.21", package.GetProperty("version").GetString());
}
finally
{
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleBunResolveAsync_NotifiesWhenInventoryMissing()
{
var originalExit = Environment.ExitCode;
var backend = new StubBackendClient(new JobTriggerResult(true, "ok", null, null));
var provider = BuildServiceProvider(backend);
try
{
var output = await CaptureTestConsoleAsync(async _ =>
{
await CommandHandlers.HandleBunResolveAsync(
provider,
imageReference: null,
scanId: "scan-missing-bun",
format: "table",
verbose: false,
cancellationToken: CancellationToken.None);
});
Assert.Equal(0, Environment.ExitCode);
Assert.Contains("not available", output.Combined, StringComparison.OrdinalIgnoreCase);
}
finally
{
Environment.ExitCode = originalExit;
}
}
[Fact]
public async Task HandleAdviseRunAsync_WritesOutputAndSetsExitCode()
{
@@ -4081,6 +4237,84 @@ spec:
packages);
}
private static void CreateBunWorkspace(string root)
{
var packageJson = """
{
"name": "test-bun-app",
"version": "1.0.0",
"dependencies": {
"lodash": "4.17.21",
"express": "4.18.2"
},
"devDependencies": {
"typescript": "5.3.3"
}
}
""";
File.WriteAllText(Path.Combine(root, "package.json"), packageJson);
var bunLock = """
{
"lockfileVersion": 0,
"packages": {
"lodash": ["lodash@4.17.21", { "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDE+k+xyz=" }],
"express": ["express@4.18.2", { "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", "integrity": "sha512-expr+k+abc=" }],
"typescript": ["typescript@5.3.3", { "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-ts+k+def=" }]
},
"workspaces": {}
}
""";
File.WriteAllText(Path.Combine(root, "bun.lock"), bunLock);
var nodeModules = Path.Combine(root, "node_modules");
Directory.CreateDirectory(nodeModules);
var lodashDir = Path.Combine(nodeModules, "lodash");
Directory.CreateDirectory(lodashDir);
File.WriteAllText(Path.Combine(lodashDir, "package.json"), """{"name":"lodash","version":"4.17.21"}""");
var expressDir = Path.Combine(nodeModules, "express");
Directory.CreateDirectory(expressDir);
File.WriteAllText(Path.Combine(expressDir, "package.json"), """{"name":"express","version":"4.18.2"}""");
var typescriptDir = Path.Combine(nodeModules, "typescript");
Directory.CreateDirectory(typescriptDir);
File.WriteAllText(Path.Combine(typescriptDir, "package.json"), """{"name":"typescript","version":"5.3.3"}""");
}
private static BunPackageItem CreateBunPackageItem(
string name,
string? version = null,
string? source = null,
bool? isDev = null,
bool? isDirect = null,
IDictionary<string, string?>? metadata = null)
{
return new BunPackageItem(
name,
version,
source ?? "registry",
$"https://registry.npmjs.org/{name}/-/{name}-{version ?? "1.0.0"}.tgz",
"sha512-abc123=",
isDev,
isDirect,
IsPatched: null,
CustomRegistry: null,
metadata ?? new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase));
}
private static BunPackageInventory CreateBunInventory(
string scanId,
IReadOnlyList<BunPackageItem> packages,
string? imageDigest = null)
{
return new BunPackageInventory(
scanId,
imageDigest ?? "sha256:bun-inventory",
DateTimeOffset.UtcNow,
packages);
}
private static string ComputeSha256Base64(string path)
{
@@ -4165,6 +4399,9 @@ spec:
public RubyPackageInventoryModel? RubyInventory { get; set; }
public Exception? RubyInventoryException { get; set; }
public string? LastRubyPackagesScanId { get; private set; }
public BunPackageInventory? BunInventory { get; set; }
public Exception? BunInventoryException { get; set; }
public string? LastBunPackagesScanId { get; private set; }
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>();
@@ -4415,6 +4652,17 @@ spec:
return Task.FromResult(RubyInventory);
}
public Task<BunPackageInventory?> GetBunPackagesAsync(string scanId, CancellationToken cancellationToken)
{
LastBunPackagesScanId = scanId;
if (BunInventoryException is not null)
{
throw BunInventoryException;
}
return Task.FromResult(BunInventory);
}
public Task<AdvisoryPipelinePlanResponseModel> CreateAdvisoryPipelinePlanAsync(AdvisoryAiTaskType taskType, AdvisoryPipelinePlanRequestModel request, CancellationToken cancellationToken)
{
AdvisoryPlanRequests.Add((taskType, request));