audit notes work completed, test fixes work (95% done), new sprints, new data sources setup and configuration

This commit is contained in:
master
2026-01-14 10:48:00 +02:00
parent d7be6ba34b
commit 95d5898650
379 changed files with 40695 additions and 19041 deletions

View File

@@ -1,5 +1,7 @@
namespace StellaOps.Interop.Tests.CycloneDx;
using Xunit.Sdk;
[Trait("Category", "Interop")]
[Trait("Format", "CycloneDX")]
public class CycloneDxRoundTripTests : IClassFixture<InteropTestHarness>
@@ -78,8 +80,7 @@ public class CycloneDxRoundTripTests : IClassFixture<InteropTestHarness>
// Skip if not in CI - cosign requires credentials
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")))
{
// Skip in local dev
return;
throw SkipException.ForSkip("Cosign attestation requires CI credentials.");
}
// Generate SBOM

View File

@@ -3,6 +3,8 @@ namespace StellaOps.Interop.Tests;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Interop;
using Xunit.Sdk;
/// <summary>
/// Test harness for SBOM interoperability testing.
@@ -12,6 +14,7 @@ public sealed class InteropTestHarness : IAsyncLifetime
{
private readonly ToolManager _toolManager;
private readonly string _workDir;
private string? _skipReason;
public InteropTestHarness()
{
@@ -23,6 +26,20 @@ public sealed class InteropTestHarness : IAsyncLifetime
{
Directory.CreateDirectory(_workDir);
var missingTools = new List<string>();
if (!_toolManager.IsToolAvailable("syft"))
missingTools.Add("syft");
if (!_toolManager.IsToolAvailable("grype"))
missingTools.Add("grype");
if (!_toolManager.IsToolAvailable("cosign"))
missingTools.Add("cosign");
if (missingTools.Count > 0)
{
_skipReason = $"Interop tools missing: {string.Join(", ", missingTools)}";
return;
}
// Verify tools are available
await _toolManager.VerifyToolAsync("syft", "--version");
await _toolManager.VerifyToolAsync("grype", "--version");
@@ -37,6 +54,7 @@ public sealed class InteropTestHarness : IAsyncLifetime
SbomFormat format,
CancellationToken ct = default)
{
EnsureToolsAvailable();
var formatArg = format switch
{
SbomFormat.CycloneDx16 => "cyclonedx-json",
@@ -72,6 +90,7 @@ public sealed class InteropTestHarness : IAsyncLifetime
SbomFormat format,
CancellationToken ct = default)
{
EnsureToolsAvailable();
var formatArg = format switch
{
SbomFormat.CycloneDx16 => "cyclonedx",
@@ -107,6 +126,7 @@ public sealed class InteropTestHarness : IAsyncLifetime
string imageRef,
CancellationToken ct = default)
{
EnsureToolsAvailable();
var result = await _toolManager.RunAsync(
"cosign",
$"attest --predicate {sbomPath} --type cyclonedx {imageRef} --yes",
@@ -125,6 +145,7 @@ public sealed class InteropTestHarness : IAsyncLifetime
string sbomPath,
CancellationToken ct = default)
{
EnsureToolsAvailable();
var outputPath = Path.Combine(_workDir, "grype-findings.json");
var result = await _toolManager.RunAsync(
"grype",
@@ -196,6 +217,12 @@ public sealed class InteropTestHarness : IAsyncLifetime
// For now, return empty list
return Array.Empty<GrypeFinding>();
}
private void EnsureToolsAvailable()
{
if (!string.IsNullOrWhiteSpace(_skipReason))
throw SkipException.ForSkip(_skipReason);
}
}

View File

@@ -6,13 +6,18 @@
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>StellaOps.Interop.Tests</RootNamespace>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\\..\\..\\__Libraries\\StellaOps.Interop\\StellaOps.Interop.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="FluentAssertions" />
</ItemGroup>
</Project>
</Project>

View File

@@ -8,3 +8,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
| AUDIT-0371-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.Interop.Tests. |
| AUDIT-0371-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.Interop.Tests. |
| AUDIT-0371-A | DONE | Waived (test project; revalidated 2026-01-07). |
| AUDIT-TESTGAP-CORELIB-INTEROP-0001 | DONE | Added ToolManager unit tests + skip gating (2026-01-13). |

View File

@@ -1,83 +0,0 @@
// -----------------------------------------------------------------------------
// ToolManager.cs
// Sprint: SPRINT_5100_0003_0001_sbom_interop_roundtrip
// Task: T1 - Interop Test Harness
// Description: Manages execution of external tools (Syft, Grype, cosign).
// -----------------------------------------------------------------------------
using System.Diagnostics;
namespace StellaOps.Interop.Tests;
public sealed class ToolManager
{
private readonly string _workDir;
public ToolManager(string workDir)
{
_workDir = workDir;
}
public async Task VerifyToolAsync(string tool, string versionArg)
{
var result = await RunAsync(tool, versionArg, CancellationToken.None);
if (!result.Success)
{
throw new InvalidOperationException(
$"Tool '{tool}' is not available or failed verification: {result.Error}");
}
}
public async Task<ToolResult> RunAsync(
string tool,
string arguments,
CancellationToken ct,
int timeoutSeconds = 300)
{
try
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = tool,
Arguments = arguments,
WorkingDirectory = _workDir,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
var outputTask = process.StandardOutput.ReadToEndAsync(ct);
var errorTask = process.StandardError.ReadToEndAsync(ct);
var completed = await Task.WhenAny(
process.WaitForExitAsync(ct),
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds), ct));
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
return new ToolResult(false, "", "Process timed out");
}
var output = await outputTask;
var error = await errorTask;
if (process.ExitCode != 0)
{
return new ToolResult(false, output, error);
}
return new ToolResult(true, output);
}
catch (Exception ex)
{
return new ToolResult(false, "", ex.Message);
}
}
}

View File

@@ -0,0 +1,158 @@
using System.Text;
using FluentAssertions;
using StellaOps.Interop;
using Xunit;
using Xunit.Sdk;
namespace StellaOps.Interop.Tests;
public sealed class ToolManagerTests
{
[Fact]
public void ResolveToolPath_UsesConfiguredPath()
{
var workDir = CreateTempDirectory();
try
{
var toolPath = Path.Combine(workDir, OperatingSystem.IsWindows() ? "stub-tool.exe" : "stub-tool");
File.WriteAllText(toolPath, "stub", Encoding.ASCII);
var toolPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["stub-tool"] = toolPath
};
var manager = new ToolManager(workDir, toolPaths);
manager.ResolveToolPath("stub-tool").Should().Be(toolPath);
}
finally
{
DeleteDirectory(workDir);
}
}
[Fact]
public void ResolveToolPath_UsesPathProbe()
{
var workDir = CreateTempDirectory();
var toolDir = CreateTempDirectory();
var originalPath = Environment.GetEnvironmentVariable("PATH");
try
{
var toolFile = OperatingSystem.IsWindows() ? "probe-tool.exe" : "probe-tool";
var toolPath = Path.Combine(toolDir, toolFile);
File.WriteAllText(toolPath, "stub", Encoding.ASCII);
Environment.SetEnvironmentVariable("PATH", toolDir);
var manager = new ToolManager(workDir);
manager.ResolveToolPath("probe-tool").Should().Be(toolPath);
}
finally
{
Environment.SetEnvironmentVariable("PATH", originalPath);
DeleteDirectory(toolDir);
DeleteDirectory(workDir);
}
}
[Fact]
public void FindOnPath_ReturnsNull_WhenPathMissing()
{
var originalPath = Environment.GetEnvironmentVariable("PATH");
try
{
Environment.SetEnvironmentVariable("PATH", string.Empty);
ToolManager.FindOnPath("missing-tool").Should().BeNull();
}
finally
{
Environment.SetEnvironmentVariable("PATH", originalPath);
}
}
[Fact]
public async Task RunAsync_ReturnsFailure_WhenToolMissing()
{
var workDir = CreateTempDirectory();
try
{
var manager = new ToolManager(workDir);
var result = await manager.RunAsync("missing-tool", "--version", CancellationToken.None);
result.Success.Should().BeFalse();
result.Error.Should().Contain("Tool not found");
result.ExitCode.Should().Be(-1);
}
finally
{
DeleteDirectory(workDir);
}
}
[Fact]
public async Task RunAsync_ReturnsSuccess_WhenShellExecutesScript()
{
var workDir = CreateTempDirectory();
try
{
var scriptPath = WriteShellScript(workDir);
var shellPath = ResolveShellPath();
if (string.IsNullOrWhiteSpace(shellPath) || !File.Exists(shellPath))
throw SkipException.ForSkip("Shell not available for interop tool test.");
var toolPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["shell"] = shellPath
};
var args = OperatingSystem.IsWindows()
? $"/c \"{scriptPath}\""
: $"\"{scriptPath}\"";
var manager = new ToolManager(workDir, toolPaths);
var result = await manager.RunAsync("shell", args, CancellationToken.None);
result.Success.Should().BeTrue();
result.StdOut.Should().Contain("ok");
}
finally
{
DeleteDirectory(workDir);
}
}
private static string ResolveShellPath()
=> OperatingSystem.IsWindows()
? Environment.GetEnvironmentVariable("ComSpec") ?? string.Empty
: "/bin/sh";
private static string WriteShellScript(string directory)
{
var scriptName = OperatingSystem.IsWindows() ? "interop-tool.cmd" : "interop-tool.sh";
var scriptPath = Path.Combine(directory, scriptName);
var content = OperatingSystem.IsWindows()
? "@echo off\r\necho ok\r\nexit /b 0\r\n"
: "#!/bin/sh\n\necho ok\nexit 0\n";
File.WriteAllText(scriptPath, content, Encoding.ASCII);
return scriptPath;
}
private static string CreateTempDirectory()
{
var path = Path.Combine(Path.GetTempPath(), $"interop-tool-{Guid.NewGuid():N}");
Directory.CreateDirectory(path);
return path;
}
private static void DeleteDirectory(string path)
{
if (Directory.Exists(path))
Directory.Delete(path, recursive: true);
}
}