Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism. - Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions. - Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests. - Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
This commit is contained in:
@@ -23,6 +23,8 @@ using StellaOps.Cli.Services.Models.AdvisoryAi;
|
||||
using StellaOps.Cli.Services.Models.Bun;
|
||||
using StellaOps.Cli.Services.Models.Ruby;
|
||||
using StellaOps.Cli.Services.Models.Transport;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Digests;
|
||||
|
||||
namespace StellaOps.Cli.Services;
|
||||
|
||||
@@ -44,16 +46,23 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly StellaOpsCliOptions _options;
|
||||
private readonly ILogger<BackendOperationsClient> _logger;
|
||||
private readonly ICryptoHash _cryptoHash;
|
||||
private readonly IStellaOpsTokenClient? _tokenClient;
|
||||
private readonly object _tokenSync = new();
|
||||
private string? _cachedAccessToken;
|
||||
private DateTimeOffset _cachedAccessTokenExpiresAt = DateTimeOffset.MinValue;
|
||||
|
||||
public BackendOperationsClient(HttpClient httpClient, StellaOpsCliOptions options, ILogger<BackendOperationsClient> logger, IStellaOpsTokenClient? tokenClient = null)
|
||||
public BackendOperationsClient(
|
||||
HttpClient httpClient,
|
||||
StellaOpsCliOptions options,
|
||||
ILogger<BackendOperationsClient> logger,
|
||||
ICryptoHash cryptoHash,
|
||||
IStellaOpsTokenClient? tokenClient = null)
|
||||
{
|
||||
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
||||
_tokenClient = tokenClient;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_options.BackendUrl) && httpClient.BaseAddress is null)
|
||||
@@ -305,14 +314,19 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
var normalizedAlgorithm = string.IsNullOrWhiteSpace(expectedDigestAlgorithm)
|
||||
? null
|
||||
: expectedDigestAlgorithm.Trim();
|
||||
var normalizedDigest = NormalizeExpectedDigest(expectedDigest);
|
||||
var expectedDigestRaw = string.IsNullOrWhiteSpace(expectedDigest) ? null : expectedDigest.Trim();
|
||||
string? expectedSha256Hex = null;
|
||||
if (string.Equals(normalizedAlgorithm, "sha256", StringComparison.OrdinalIgnoreCase) && expectedDigestRaw is not null)
|
||||
{
|
||||
expectedSha256Hex = Sha256Digest.ExtractHex(expectedDigestRaw, requirePrefix: false, parameterName: nameof(expectedDigest));
|
||||
}
|
||||
|
||||
if (File.Exists(fullPath)
|
||||
&& string.Equals(normalizedAlgorithm, "sha256", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.IsNullOrWhiteSpace(normalizedDigest))
|
||||
&& expectedSha256Hex is not null)
|
||||
{
|
||||
var existingDigest = await ComputeSha256Async(fullPath, cancellationToken).ConfigureAwait(false);
|
||||
if (string.Equals(existingDigest, normalizedDigest, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(existingDigest, expectedSha256Hex, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var info = new FileInfo(fullPath);
|
||||
_logger.LogDebug("Export {ExportId} already present at {Path}; digest matches.", exportId, fullPath);
|
||||
@@ -345,15 +359,15 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(normalizedAlgorithm) && !string.IsNullOrWhiteSpace(normalizedDigest))
|
||||
if (!string.IsNullOrWhiteSpace(normalizedAlgorithm) && expectedDigestRaw is not null)
|
||||
{
|
||||
if (string.Equals(normalizedAlgorithm, "sha256", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var computed = await ComputeSha256Async(tempPath, cancellationToken).ConfigureAwait(false);
|
||||
if (!string.Equals(computed, normalizedDigest, StringComparison.OrdinalIgnoreCase))
|
||||
if (expectedSha256Hex is null || !string.Equals(computed, expectedSha256Hex, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
throw new InvalidOperationException($"Export digest mismatch. Expected sha256:{normalizedDigest}, computed sha256:{computed}.");
|
||||
throw new InvalidOperationException($"Export digest mismatch. Expected sha256:{expectedSha256Hex}, computed sha256:{computed}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3020,35 +3034,31 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? NormalizeExpectedDigest(string? digest)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(digest))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var trimmed = digest.Trim();
|
||||
return trimmed.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase)
|
||||
? trimmed[7..]
|
||||
: trimmed;
|
||||
}
|
||||
|
||||
private async Task<string> ValidateDigestAsync(string filePath, string? expectedDigest, CancellationToken cancellationToken)
|
||||
{
|
||||
string digestHex;
|
||||
await using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
digestHex = Convert.ToHexString(hash).ToLowerInvariant();
|
||||
digestHex = await _cryptoHash.ComputeHashHexAsync(stream, HashAlgorithms.Sha256, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(expectedDigest))
|
||||
{
|
||||
var normalized = NormalizeDigest(expectedDigest);
|
||||
if (!normalized.Equals(digestHex, StringComparison.OrdinalIgnoreCase))
|
||||
string expectedHex;
|
||||
try
|
||||
{
|
||||
expectedHex = Sha256Digest.ExtractHex(expectedDigest, requirePrefix: false, parameterName: "X-StellaOps-Digest");
|
||||
}
|
||||
catch (Exception ex) when (ex is ArgumentException or FormatException)
|
||||
{
|
||||
File.Delete(filePath);
|
||||
throw new InvalidOperationException($"Scanner digest mismatch. Expected sha256:{normalized}, calculated sha256:{digestHex}.");
|
||||
throw new InvalidOperationException($"Scanner digest header is invalid: {ex.Message}", ex);
|
||||
}
|
||||
|
||||
if (!expectedHex.Equals(digestHex, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
throw new InvalidOperationException($"Scanner digest mismatch. Expected sha256:{expectedHex}, calculated sha256:{digestHex}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -3059,21 +3069,10 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient
|
||||
return digestHex;
|
||||
}
|
||||
|
||||
private static string NormalizeDigest(string digest)
|
||||
{
|
||||
if (digest.StartsWith("sha256:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return digest[7..];
|
||||
}
|
||||
|
||||
return digest;
|
||||
}
|
||||
|
||||
private static async Task<string> ComputeSha256Async(string filePath, CancellationToken cancellationToken)
|
||||
private async Task<string> ComputeSha256Async(string filePath, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var stream = File.OpenRead(filePath);
|
||||
var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||
return await _cryptoHash.ComputeHashHexAsync(stream, HashAlgorithms.Sha256, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task ValidateSignatureAsync(string? signatureHeader, string digestHex, bool verbose, CancellationToken cancellationToken)
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
@@ -22,19 +21,25 @@ using StellaOps.Cli.Services.Models;
|
||||
using StellaOps.Cli.Services.Models.Transport;
|
||||
using StellaOps.Cli.Tests.Testing;
|
||||
using StellaOps.Scanner.EntryTrace;
|
||||
using StellaOps.Cryptography;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Cli.Tests.Services;
|
||||
|
||||
public sealed class BackendOperationsClientTests
|
||||
{
|
||||
private static readonly ICryptoHash CryptoHash = DefaultCryptoHash.CreateForTests();
|
||||
|
||||
private static string ComputeSha256Hex(ReadOnlySpan<byte> data)
|
||||
=> CryptoHash.ComputeHashHex(data, HashAlgorithms.Sha256);
|
||||
|
||||
[Fact]
|
||||
public async Task DownloadScannerAsync_VerifiesDigestAndWritesMetadata()
|
||||
{
|
||||
using var temp = new TempDirectory();
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes("scanner-blob");
|
||||
var digestHex = Convert.ToHexString(SHA256.HashData(contentBytes)).ToLowerInvariant();
|
||||
var digestHex = ComputeSha256Hex(contentBytes);
|
||||
|
||||
var handler = new StubHttpMessageHandler((request, _) =>
|
||||
{
|
||||
@@ -63,7 +68,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var targetPath = Path.Combine(temp.Path, "scanner.tar.gz");
|
||||
var result = await client.DownloadScannerAsync("stable", targetPath, overwrite: false, verbose: true, CancellationToken.None);
|
||||
@@ -85,6 +90,7 @@ public sealed class BackendOperationsClientTests
|
||||
using var temp = new TempDirectory();
|
||||
|
||||
var contentBytes = Encoding.UTF8.GetBytes("scanner-data");
|
||||
var wrongDigestHex = ComputeSha256Hex(Encoding.UTF8.GetBytes("wrong-data"));
|
||||
var handler = new StubHttpMessageHandler((request, _) =>
|
||||
{
|
||||
var response = new HttpResponseMessage(HttpStatusCode.OK)
|
||||
@@ -92,7 +98,7 @@ public sealed class BackendOperationsClientTests
|
||||
Content = new ByteArrayContent(contentBytes),
|
||||
RequestMessage = request
|
||||
};
|
||||
response.Headers.Add("X-StellaOps-Digest", "sha256:deadbeef");
|
||||
response.Headers.Add("X-StellaOps-Digest", $"sha256:{wrongDigestHex}");
|
||||
return response;
|
||||
});
|
||||
|
||||
@@ -109,7 +115,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var targetPath = Path.Combine(temp.Path, "scanner.tar.gz");
|
||||
|
||||
@@ -123,7 +129,7 @@ public sealed class BackendOperationsClientTests
|
||||
using var temp = new TempDirectory();
|
||||
|
||||
var successBytes = Encoding.UTF8.GetBytes("success");
|
||||
var digestHex = Convert.ToHexString(SHA256.HashData(successBytes)).ToLowerInvariant();
|
||||
var digestHex = ComputeSha256Hex(successBytes);
|
||||
var attempts = 0;
|
||||
|
||||
var handler = new StubHttpMessageHandler(
|
||||
@@ -161,7 +167,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var targetPath = Path.Combine(temp.Path, "scanner.tar.gz");
|
||||
var result = await client.DownloadScannerAsync("stable", targetPath, overwrite: false, verbose: false, CancellationToken.None);
|
||||
@@ -212,7 +218,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
await client.UploadScanResultsAsync(filePath, CancellationToken.None);
|
||||
|
||||
@@ -250,7 +256,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => client.UploadScanResultsAsync(filePath, CancellationToken.None));
|
||||
Assert.Equal(2, attempts);
|
||||
@@ -316,7 +322,7 @@ public sealed class BackendOperationsClientTests
|
||||
BackendUrl = "https://scanner.example"
|
||||
};
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.GetEntryTraceAsync(scanId, CancellationToken.None);
|
||||
|
||||
@@ -345,7 +351,7 @@ public sealed class BackendOperationsClientTests
|
||||
BackendUrl = "https://scanner.example"
|
||||
};
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.GetEntryTraceAsync("scan-missing", CancellationToken.None);
|
||||
Assert.Null(result);
|
||||
@@ -379,7 +385,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://concelier.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.TriggerJobAsync("export:json", new Dictionary<string, object?>(), CancellationToken.None);
|
||||
|
||||
@@ -414,7 +420,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://concelier.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.TriggerJobAsync("export:json", new Dictionary<string, object?>(), CancellationToken.None);
|
||||
|
||||
@@ -467,7 +473,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var tokenClient = new StubTokenClient();
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), tokenClient);
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash, tokenClient);
|
||||
|
||||
var result = await client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None);
|
||||
|
||||
@@ -517,7 +523,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var tokenClient = new StubTokenClient();
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), tokenClient);
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash, tokenClient);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None));
|
||||
Assert.Contains("Authority.BackfillReason", exception.Message, StringComparison.Ordinal);
|
||||
@@ -570,7 +576,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var tokenClient = new StubTokenClient();
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), tokenClient);
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash, tokenClient);
|
||||
|
||||
var result = await client.TriggerJobAsync("test", new Dictionary<string, object?>(), CancellationToken.None);
|
||||
|
||||
@@ -643,7 +649,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var labels = new ReadOnlyDictionary<string, string>(new Dictionary<string, string> { ["app"] = "payments" });
|
||||
var imagesList = new ReadOnlyCollection<string>(new List<string>
|
||||
@@ -693,8 +699,8 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var bundleBytes = Encoding.UTF8.GetBytes("bundle-data");
|
||||
var manifestBytes = Encoding.UTF8.GetBytes("{\"artifacts\":[]}");
|
||||
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
|
||||
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
|
||||
var bundleDigest = ComputeSha256Hex(bundleBytes);
|
||||
var manifestDigest = ComputeSha256Hex(manifestBytes);
|
||||
|
||||
var metadataPayload = JsonSerializer.Serialize(new
|
||||
{
|
||||
@@ -762,7 +768,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.DownloadOfflineKitAsync(null, temp.Path, overwrite: false, resume: false, CancellationToken.None);
|
||||
|
||||
@@ -785,8 +791,8 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var bundleBytes = Encoding.UTF8.GetBytes("partial-download-data");
|
||||
var manifestBytes = Encoding.UTF8.GetBytes("{\"manifest\":true}");
|
||||
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
|
||||
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
|
||||
var bundleDigest = ComputeSha256Hex(bundleBytes);
|
||||
var manifestDigest = ComputeSha256Hex(manifestBytes);
|
||||
|
||||
var metadataJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
@@ -842,7 +848,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var result = await client.DownloadOfflineKitAsync(null, temp.Path, overwrite: false, resume: true, CancellationToken.None);
|
||||
|
||||
@@ -862,8 +868,8 @@ public sealed class BackendOperationsClientTests
|
||||
await File.WriteAllBytesAsync(bundlePath, bundleBytes);
|
||||
await File.WriteAllBytesAsync(manifestPath, manifestBytes);
|
||||
|
||||
var bundleDigest = Convert.ToHexString(SHA256.HashData(bundleBytes)).ToLowerInvariant();
|
||||
var manifestDigest = Convert.ToHexString(SHA256.HashData(manifestBytes)).ToLowerInvariant();
|
||||
var bundleDigest = ComputeSha256Hex(bundleBytes);
|
||||
var manifestDigest = ComputeSha256Hex(manifestBytes);
|
||||
|
||||
var metadata = new OfflineKitMetadataDocument
|
||||
{
|
||||
@@ -898,7 +904,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var request = new OfflineKitImportRequest(
|
||||
bundlePath,
|
||||
@@ -982,7 +988,7 @@ public sealed class BackendOperationsClientTests
|
||||
};
|
||||
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var status = await client.GetOfflineKitStatusAsync(CancellationToken.None);
|
||||
|
||||
@@ -1126,7 +1132,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://policy.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var sbomSet = new ReadOnlyCollection<string>(new List<string> { "sbom:A", "sbom:B" });
|
||||
var environment = new ReadOnlyDictionary<string, object?>(new Dictionary<string, object?>(StringComparer.Ordinal)
|
||||
@@ -1193,7 +1199,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://policy.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var input = new PolicySimulationInput(
|
||||
null,
|
||||
@@ -1257,7 +1263,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://policy.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var request = new PolicyActivationRequest(
|
||||
RunNow: true,
|
||||
@@ -1321,7 +1327,7 @@ public sealed class BackendOperationsClientTests
|
||||
|
||||
var options = new StellaOpsCliOptions { BackendUrl = "https://policy.example" };
|
||||
var loggerFactory = LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Debug));
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>());
|
||||
var client = new BackendOperationsClient(httpClient, options, loggerFactory.CreateLogger<BackendOperationsClient>(), CryptoHash);
|
||||
|
||||
var request = new PolicyActivationRequest(false, null, null, false, null, null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user