tests fixes and some product advisories tunes ups
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Cryptography;
|
||||
@@ -182,7 +183,8 @@ public static class MirrorBundleSigningExtensions
|
||||
return JsonSerializer.Serialize(signature, new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,16 @@ public sealed class OfflineBundlePackager : IOfflineBundlePackager
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Bundle {BundlePath} appears to be corrupted", bundlePath);
|
||||
return new BundleVerificationResult
|
||||
{
|
||||
IsValid = false,
|
||||
Issues = new[] { $"Bundle appears to be corrupted: {ex.Message}" },
|
||||
VerifiedAt = _timeProvider.GetUtcNow()
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Determinism;
|
||||
@@ -14,6 +16,18 @@ namespace StellaOps.ExportCenter.Snapshots;
|
||||
/// </summary>
|
||||
public sealed class ExportSnapshotService : IExportSnapshotService
|
||||
{
|
||||
/// <summary>
|
||||
/// Export serialization options: canonical format with indentation for readability.
|
||||
/// Uses same property naming (camelCase) as the canonical format for ID verification compatibility.
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions ExportOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
private readonly ISnapshotService _snapshotService;
|
||||
private readonly IKnowledgeSourceResolver _sourceResolver;
|
||||
private readonly ILogger<ExportSnapshotService> _logger;
|
||||
@@ -123,7 +137,7 @@ public sealed class ExportSnapshotService : IExportSnapshotService
|
||||
string tempDir, KnowledgeSnapshotManifest manifest, CancellationToken ct)
|
||||
{
|
||||
var manifestPath = Path.Combine(tempDir, "manifest.json");
|
||||
var json = JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true });
|
||||
var json = JsonSerializer.Serialize(manifest, ExportOptions);
|
||||
await File.WriteAllTextAsync(manifestPath, json, ct).ConfigureAwait(false);
|
||||
|
||||
// Write signed envelope if signature present
|
||||
@@ -143,13 +157,13 @@ public sealed class ExportSnapshotService : IExportSnapshotService
|
||||
payloadType = "application/vnd.stellaops.snapshot+json",
|
||||
payload = Convert.ToBase64String(
|
||||
System.Text.Encoding.UTF8.GetBytes(
|
||||
JsonSerializer.Serialize(manifest with { Signature = null }))),
|
||||
JsonSerializer.Serialize(manifest with { Signature = null }, ExportOptions))),
|
||||
signatures = new[]
|
||||
{
|
||||
new { keyid = "snapshot-signing-key", sig = manifest.Signature }
|
||||
}
|
||||
};
|
||||
return JsonSerializer.Serialize(envelope, new JsonSerializerOptions { WriteIndented = true });
|
||||
return JsonSerializer.Serialize(envelope, ExportOptions);
|
||||
}
|
||||
|
||||
private async Task<List<BundledFile>> BundleSourcesAsync(
|
||||
@@ -228,7 +242,7 @@ public sealed class ExportSnapshotService : IExportSnapshotService
|
||||
var metaDir = Path.Combine(tempDir, "META");
|
||||
Directory.CreateDirectory(metaDir);
|
||||
|
||||
var json = JsonSerializer.Serialize(info, new JsonSerializerOptions { WriteIndented = true });
|
||||
var json = JsonSerializer.Serialize(info, ExportOptions);
|
||||
await File.WriteAllTextAsync(Path.Combine(metaDir, "BUNDLE_INFO.json"), json, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Determinism;
|
||||
@@ -13,6 +14,12 @@ namespace StellaOps.ExportCenter.Snapshots;
|
||||
/// </summary>
|
||||
public sealed class ImportSnapshotService : IImportSnapshotService
|
||||
{
|
||||
private static readonly JsonSerializerOptions ImportOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
private readonly ISnapshotService _snapshotService;
|
||||
private readonly ISnapshotStore _snapshotStore;
|
||||
private readonly ILogger<ImportSnapshotService> _logger;
|
||||
@@ -67,7 +74,7 @@ public sealed class ImportSnapshotService : IImportSnapshotService
|
||||
return ImportResult.Fail("Bundle missing manifest.json");
|
||||
|
||||
var manifestJson = await File.ReadAllTextAsync(manifestPath, ct).ConfigureAwait(false);
|
||||
var manifest = JsonSerializer.Deserialize<KnowledgeSnapshotManifest>(manifestJson)
|
||||
var manifest = JsonSerializer.Deserialize<KnowledgeSnapshotManifest>(manifestJson, ImportOptions)
|
||||
?? throw new InvalidOperationException("Failed to parse manifest");
|
||||
|
||||
// Verify manifest signature if sealed
|
||||
|
||||
@@ -30,7 +30,12 @@ internal sealed partial class MigrationScript
|
||||
|
||||
public static bool TryCreate(string resourceName, string sql, [NotNullWhen(true)] out MigrationScript? script)
|
||||
{
|
||||
var fileName = resourceName.Split('.').Last();
|
||||
// Resource names are like: StellaOps.ExportCenter.Infrastructure.Db.Migrations.001_initial_schema.sql
|
||||
// We need to extract "001_initial_schema.sql" (last two segments joined)
|
||||
var parts = resourceName.Split('.');
|
||||
var fileName = parts.Length >= 2
|
||||
? $"{parts[^2]}.{parts[^1]}"
|
||||
: parts.LastOrDefault() ?? string.Empty;
|
||||
var match = VersionRegex.Match(fileName);
|
||||
|
||||
if (!match.Success || !int.TryParse(match.Groups["version"].Value, out var version))
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.ExportCenter.WebService.Api;
|
||||
using Xunit;
|
||||
|
||||
@@ -21,6 +23,8 @@ public sealed class ExportApiServiceCollectionExtensionsTests
|
||||
public void AddExportApiServices_AllowsExplicitInMemoryRegistration()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
|
||||
services.AddSingleton(typeof(ILogger<>), typeof(NullLogger<>));
|
||||
|
||||
services.AddExportApiServices(_ => { }, allowInMemoryRepositories: true);
|
||||
var provider = services.BuildServiceProvider();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
using StellaOps.ExportCenter.Infrastructure.Db;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.ExportCenter.Tests.Db;
|
||||
@@ -11,15 +11,14 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "StellaOps.ExportCenter.Infrastructure.Db.Migrations.001_initial_schema.sql";
|
||||
var sql = "CREATE TABLE test (id int);";
|
||||
|
||||
var result = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
var result = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.NotNull(script);
|
||||
var scriptValue = script!;
|
||||
Assert.Equal(1, scriptValue.Version);
|
||||
Assert.Equal("001_initial_schema.sql", scriptValue.Name);
|
||||
Assert.Equal(sql, scriptValue.Sql);
|
||||
Assert.NotEmpty(scriptValue.Sha256);
|
||||
Assert.Equal(1, script.Version);
|
||||
Assert.Equal("001_initial_schema.sql", script.Name);
|
||||
Assert.Equal(sql, script.Sql);
|
||||
Assert.NotEmpty(script.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -28,11 +27,11 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.123_migration.sql";
|
||||
var sql = "SELECT 1;";
|
||||
|
||||
var result = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
var result = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.NotNull(script);
|
||||
Assert.Equal(123, script!.Version);
|
||||
Assert.Equal(123, script.Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,11 +40,11 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.1000_big_migration.sql";
|
||||
var sql = "SELECT 1;";
|
||||
|
||||
var result = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
var result = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.NotNull(script);
|
||||
Assert.Equal(1000, script!.Version);
|
||||
Assert.Equal(1000, script.Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -54,7 +53,7 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.invalid.sql";
|
||||
var sql = "SELECT 1;";
|
||||
|
||||
var result = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
var result = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Null(script);
|
||||
@@ -66,7 +65,7 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.no_version.sql";
|
||||
var sql = "SELECT 1;";
|
||||
|
||||
var result = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
var result = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Null(script);
|
||||
@@ -78,12 +77,12 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.001_test.sql";
|
||||
var sql = "CREATE TABLE test (id int);";
|
||||
|
||||
_ = TryCreateMigrationScript(resourceName, sql, out var script1);
|
||||
_ = TryCreateMigrationScript(resourceName, sql, out var script2);
|
||||
_ = MigrationScript.TryCreate(resourceName, sql, out var script1);
|
||||
_ = MigrationScript.TryCreate(resourceName, sql, out var script2);
|
||||
|
||||
Assert.NotNull(script1);
|
||||
Assert.NotNull(script2);
|
||||
Assert.Equal(script1!.Sha256, script2!.Sha256);
|
||||
Assert.Equal(script1.Sha256, script2.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -93,12 +92,12 @@ public sealed class MigrationScriptTests
|
||||
var sqlUnix = "CREATE TABLE test\n(id int);";
|
||||
var sqlWindows = "CREATE TABLE test\r\n(id int);";
|
||||
|
||||
_ = TryCreateMigrationScript(resourceName, sqlUnix, out var scriptUnix);
|
||||
_ = TryCreateMigrationScript(resourceName, sqlWindows, out var scriptWindows);
|
||||
_ = MigrationScript.TryCreate(resourceName, sqlUnix, out var scriptUnix);
|
||||
_ = MigrationScript.TryCreate(resourceName, sqlWindows, out var scriptWindows);
|
||||
|
||||
Assert.NotNull(scriptUnix);
|
||||
Assert.NotNull(scriptWindows);
|
||||
Assert.Equal(scriptUnix!.Sha256, scriptWindows!.Sha256);
|
||||
Assert.Equal(scriptUnix.Sha256, scriptWindows.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -108,12 +107,12 @@ public sealed class MigrationScriptTests
|
||||
var sql1 = "CREATE TABLE test1 (id int);";
|
||||
var sql2 = "CREATE TABLE test2 (id int);";
|
||||
|
||||
_ = TryCreateMigrationScript(resourceName, sql1, out var script1);
|
||||
_ = TryCreateMigrationScript(resourceName, sql2, out var script2);
|
||||
_ = MigrationScript.TryCreate(resourceName, sql1, out var script1);
|
||||
_ = MigrationScript.TryCreate(resourceName, sql2, out var script2);
|
||||
|
||||
Assert.NotNull(script1);
|
||||
Assert.NotNull(script2);
|
||||
Assert.NotEqual(script1!.Sha256, script2!.Sha256);
|
||||
Assert.NotEqual(script1.Sha256, script2.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -122,34 +121,9 @@ public sealed class MigrationScriptTests
|
||||
var resourceName = "Test.Db.Migrations.001_test.sql";
|
||||
var sql = "SELECT 1;";
|
||||
|
||||
_ = TryCreateMigrationScript(resourceName, sql, out var script);
|
||||
_ = MigrationScript.TryCreate(resourceName, sql, out var script);
|
||||
|
||||
Assert.NotNull(script);
|
||||
Assert.Matches("^[0-9a-f]{64}$", script!.Sha256);
|
||||
}
|
||||
|
||||
// Helper to access internal MigrationScript via reflection
|
||||
private static bool TryCreateMigrationScript(string resourceName, string sql, out dynamic? script)
|
||||
{
|
||||
var assembly = typeof(Infrastructure.Db.ExportCenterDataSource).Assembly;
|
||||
var scriptType = assembly.GetType("StellaOps.ExportCenter.Infrastructure.Db.MigrationScript");
|
||||
|
||||
if (scriptType is null)
|
||||
{
|
||||
script = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var method = scriptType.GetMethod("TryCreate", BindingFlags.Public | BindingFlags.Static);
|
||||
if (method is null)
|
||||
{
|
||||
script = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var parameters = new object?[] { resourceName, sql, null };
|
||||
var result = (bool)method.Invoke(null, parameters)!;
|
||||
script = parameters[2];
|
||||
return result;
|
||||
Assert.Matches("^[0-9a-f]{64}$", script.Sha256);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ public sealed class ExportDistributionLifecycleTests
|
||||
[Fact]
|
||||
public async Task ProcessExpiredDistributionsAsync_MarksExpired()
|
||||
{
|
||||
// Create distribution with past expiry
|
||||
// Create distribution with past expiry (but within in-memory repository's 24-hour retention)
|
||||
var distribution = new ExportDistribution
|
||||
{
|
||||
DistributionId = Guid.NewGuid(),
|
||||
@@ -431,8 +431,8 @@ public sealed class ExportDistributionLifecycleTests
|
||||
Status = ExportDistributionStatus.Distributed,
|
||||
Target = "test",
|
||||
ArtifactPath = "/test",
|
||||
RetentionExpiresAt = _timeProvider.GetUtcNow().AddDays(-1),
|
||||
CreatedAt = _timeProvider.GetUtcNow().AddDays(-30)
|
||||
RetentionExpiresAt = _timeProvider.GetUtcNow().AddMinutes(-5),
|
||||
CreatedAt = _timeProvider.GetUtcNow().AddHours(-1)
|
||||
};
|
||||
await _repository.CreateAsync(distribution);
|
||||
|
||||
@@ -456,9 +456,9 @@ public sealed class ExportDistributionLifecycleTests
|
||||
Status = ExportDistributionStatus.Distributed,
|
||||
Target = "test",
|
||||
ArtifactPath = "/test",
|
||||
RetentionExpiresAt = _timeProvider.GetUtcNow().AddDays(-1),
|
||||
RetentionExpiresAt = _timeProvider.GetUtcNow().AddMinutes(-5),
|
||||
MetadataJson = "{\"legalHold\":true}",
|
||||
CreatedAt = _timeProvider.GetUtcNow().AddDays(-30)
|
||||
CreatedAt = _timeProvider.GetUtcNow().AddHours(-1)
|
||||
};
|
||||
await _repository.CreateAsync(distribution);
|
||||
|
||||
|
||||
@@ -140,18 +140,31 @@ public sealed class OciReferrerDiscoveryTests
|
||||
public async Task FindRvaAttestations_ReturnsRvaArtifacts()
|
||||
{
|
||||
// Arrange
|
||||
var manifests = new[]
|
||||
var dsseManifests = new[]
|
||||
{
|
||||
new { digest = "sha256:rva1", artifactType = OciArtifactTypes.RvaDsse, mediaType = OciMediaTypes.ImageManifest, size = 100L }
|
||||
};
|
||||
var indexJson = JsonSerializer.Serialize(new
|
||||
var dsseIndexJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
schemaVersion = 2,
|
||||
mediaType = OciMediaTypes.ImageIndex,
|
||||
manifests
|
||||
manifests = dsseManifests
|
||||
});
|
||||
var emptyIndexJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
schemaVersion = 2,
|
||||
mediaType = OciMediaTypes.ImageIndex,
|
||||
manifests = Array.Empty<object>()
|
||||
});
|
||||
|
||||
var mockHandler = CreateMockHandler(HttpStatusCode.OK, indexJson);
|
||||
// Return artifacts only for DSSE filter, empty for JSON filter
|
||||
var mockHandler = new MockFallbackHandler(request =>
|
||||
{
|
||||
var url = request.RequestUri?.ToString() ?? "";
|
||||
if (url.Contains(Uri.EscapeDataString(OciArtifactTypes.RvaDsse)))
|
||||
return (HttpStatusCode.OK, dsseIndexJson);
|
||||
return (HttpStatusCode.OK, emptyIndexJson);
|
||||
});
|
||||
var discovery = new OciReferrerDiscovery(
|
||||
new HttpClient(mockHandler),
|
||||
_mockAuth.Object,
|
||||
|
||||
@@ -81,9 +81,12 @@ public class HmacDevPortalOfflineManifestSignerTests
|
||||
var payloadBytes = Encoding.UTF8.GetBytes(manifest);
|
||||
var pae = BuildPreAuthEncoding(options.PayloadType, payloadBytes);
|
||||
|
||||
// FakeCryptoHmac computes SHA256(key || data), not HMAC
|
||||
var secret = Convert.FromBase64String(options.Secret);
|
||||
using var hmac = new HMACSHA256(secret);
|
||||
var signature = hmac.ComputeHash(pae);
|
||||
var combined = new byte[secret.Length + pae.Length];
|
||||
secret.CopyTo(combined, 0);
|
||||
pae.CopyTo(combined, secret.Length);
|
||||
var signature = SHA256.HashData(combined);
|
||||
return Convert.ToBase64String(signature);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ public sealed class OfflineBundlePackagerTests : IDisposable
|
||||
|
||||
// Act
|
||||
var result1 = await _packager.CreateBundleAsync(request);
|
||||
// Advance time to ensure unique bundle ID (bundle ID includes timestamp)
|
||||
_timeProvider.Advance(TimeSpan.FromSeconds(1));
|
||||
var result2 = await _packager.CreateBundleAsync(request);
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
@@ -129,14 +131,19 @@ public sealed class AirGapReplayTests : IDisposable
|
||||
{
|
||||
var snapshot = await CreateSnapshotWithBundledSourcesAsync();
|
||||
|
||||
// Use uncompressed sources so tampering by appending data works
|
||||
// (gzip ignores trailing data after the proper footer)
|
||||
var exportResult = await _exportService.ExportAsync(snapshot.SnapshotId,
|
||||
new ExportOptions { InclusionLevel = SnapshotInclusionLevel.Portable });
|
||||
new ExportOptions { InclusionLevel = SnapshotInclusionLevel.Portable, CompressSources = false });
|
||||
_tempFiles.Add(exportResult.FilePath!);
|
||||
|
||||
// Tamper with the bundle
|
||||
var temperedPath = await TamperWithBundleAsync(exportResult.FilePath!);
|
||||
_tempFiles.Add(temperedPath);
|
||||
|
||||
// Clear store so import can proceed to checksum verification
|
||||
_snapshotStore.Clear();
|
||||
|
||||
// Import should fail with checksum verification enabled
|
||||
var importResult = await _importService.ImportAsync(temperedPath,
|
||||
new ImportOptions { VerifyChecksums = true });
|
||||
@@ -213,17 +220,23 @@ public sealed class AirGapReplayTests : IDisposable
|
||||
|
||||
private async Task<KnowledgeSnapshotManifest> CreateSnapshotAsync()
|
||||
{
|
||||
// Compute the real digest of the test content that TestKnowledgeSourceResolver will return
|
||||
const string sourceName = "test-feed";
|
||||
var content = Encoding.UTF8.GetBytes($"test-content-{sourceName}");
|
||||
var hash = SHA256.HashData(content);
|
||||
var digest = $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("stellaops-policy", "1.0.0", "abc123")
|
||||
.WithPolicy("test-policy", "1.0", "sha256:policy123")
|
||||
.WithScoring("test-scoring", "1.0", "sha256:scoring123")
|
||||
.WithSource(new KnowledgeSourceDescriptor
|
||||
{
|
||||
Name = "test-feed",
|
||||
Name = sourceName,
|
||||
Type = "advisory-feed",
|
||||
Epoch = DateTimeOffset.UtcNow.ToString("o"),
|
||||
Digest = "sha256:feed123",
|
||||
InclusionMode = SourceInclusionMode.Referenced
|
||||
Digest = digest,
|
||||
InclusionMode = SourceInclusionMode.Bundled
|
||||
});
|
||||
|
||||
return await _snapshotService.CreateSnapshotAsync(builder);
|
||||
@@ -231,16 +244,22 @@ public sealed class AirGapReplayTests : IDisposable
|
||||
|
||||
private async Task<KnowledgeSnapshotManifest> CreateSnapshotWithBundledSourcesAsync()
|
||||
{
|
||||
// Compute the real digest of the test content that TestKnowledgeSourceResolver will return
|
||||
const string sourceName = "bundled-feed";
|
||||
var content = Encoding.UTF8.GetBytes($"test-content-{sourceName}");
|
||||
var hash = SHA256.HashData(content);
|
||||
var digest = $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("stellaops-policy", "1.0.0", "abc123")
|
||||
.WithPolicy("test-policy", "1.0", "sha256:policy123")
|
||||
.WithScoring("test-scoring", "1.0", "sha256:scoring123")
|
||||
.WithSource(new KnowledgeSourceDescriptor
|
||||
{
|
||||
Name = "bundled-feed",
|
||||
Name = sourceName,
|
||||
Type = "advisory-feed",
|
||||
Epoch = DateTimeOffset.UtcNow.ToString("o"),
|
||||
Digest = "sha256:bundled123",
|
||||
Digest = digest,
|
||||
InclusionMode = SourceInclusionMode.Bundled
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Cryptography;
|
||||
@@ -135,17 +137,23 @@ public sealed class ExportSnapshotServiceTests : IDisposable
|
||||
|
||||
private async Task<KnowledgeSnapshotManifest> CreateSnapshotAsync()
|
||||
{
|
||||
// Compute the real digest of the test content that TestKnowledgeSourceResolver will return
|
||||
const string sourceName = "test-feed";
|
||||
var content = Encoding.UTF8.GetBytes($"test-content-{sourceName}");
|
||||
var hash = SHA256.HashData(content);
|
||||
var digest = $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
|
||||
|
||||
var builder = new SnapshotBuilder(_hasher)
|
||||
.WithEngine("stellaops-policy", "1.0.0", "abc123")
|
||||
.WithPolicy("test-policy", "1.0", "sha256:policy123")
|
||||
.WithScoring("test-scoring", "1.0", "sha256:scoring123")
|
||||
.WithSource(new KnowledgeSourceDescriptor
|
||||
{
|
||||
Name = "test-feed",
|
||||
Name = sourceName,
|
||||
Type = "advisory-feed",
|
||||
Epoch = DateTimeOffset.UtcNow.ToString("o"),
|
||||
Digest = "sha256:feed123",
|
||||
InclusionMode = SourceInclusionMode.Referenced
|
||||
Digest = digest,
|
||||
InclusionMode = SourceInclusionMode.Bundled
|
||||
});
|
||||
|
||||
return await _snapshotService.CreateSnapshotAsync(builder);
|
||||
|
||||
Reference in New Issue
Block a user