132 lines
5.6 KiB
C#
132 lines
5.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using StellaOps.Cryptography;
|
|
using StellaOps.EvidenceLocker.Core.Builders;
|
|
using StellaOps.EvidenceLocker.Core.Domain;
|
|
using StellaOps.EvidenceLocker.Core.Repositories;
|
|
using StellaOps.EvidenceLocker.Infrastructure.Builders;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.EvidenceLocker.Tests;
|
|
|
|
public sealed class EvidenceBundleBuilderTests
|
|
{
|
|
private readonly FakeRepository _repository = new();
|
|
private readonly IEvidenceBundleBuilder _builder;
|
|
|
|
public EvidenceBundleBuilderTests()
|
|
{
|
|
_builder = new EvidenceBundleBuilder(_repository, new MerkleTreeCalculator(new DefaultCryptoHasher(HashAlgorithms.Sha256)));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task BuildAsync_ComputesDeterministicRootAndPersists()
|
|
{
|
|
var bundleId = EvidenceBundleId.FromGuid(Guid.NewGuid());
|
|
var tenantId = TenantId.FromGuid(Guid.NewGuid());
|
|
var request = new EvidenceBundleBuildRequest(
|
|
bundleId,
|
|
tenantId,
|
|
EvidenceBundleKind.Job,
|
|
DateTimeOffset.Parse("2025-11-03T15:04:05Z"),
|
|
new Dictionary<string, string> { ["run-id"] = "job-42" },
|
|
new List<EvidenceBundleMaterial>
|
|
{
|
|
new("inputs", "config/env.json", "5a6b7c", 1024, "application/json"),
|
|
new("outputs", "reports/result.txt", "7f8e9d", 2048, "text/plain")
|
|
});
|
|
|
|
var result = await _builder.BuildAsync(request, CancellationToken.None);
|
|
|
|
Assert.Equal(EvidenceBundleStatus.Sealed, _repository.LastStatus);
|
|
Assert.Equal(bundleId, _repository.LastBundleId);
|
|
Assert.Equal(tenantId, _repository.LastTenantId);
|
|
Assert.Equal(DateTimeOffset.Parse("2025-11-03T15:04:05Z"), _repository.LastUpdatedAt);
|
|
|
|
Assert.Equal(result.RootHash, _repository.LastRootHash);
|
|
Assert.Equal(2, result.Manifest.Entries.Count);
|
|
Assert.True(result.Manifest.Entries.SequenceEqual(
|
|
result.Manifest.Entries.OrderBy(entry => entry.CanonicalPath, StringComparer.Ordinal)));
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public async Task BuildAsync_NormalizesSectionAndPath()
|
|
{
|
|
var request = new EvidenceBundleBuildRequest(
|
|
EvidenceBundleId.FromGuid(Guid.NewGuid()),
|
|
TenantId.FromGuid(Guid.NewGuid()),
|
|
EvidenceBundleKind.Evaluation,
|
|
DateTimeOffset.UtcNow,
|
|
new Dictionary<string, string>(),
|
|
new List<EvidenceBundleMaterial>
|
|
{
|
|
new(" Inputs ", "./Config/Env.JSON ", "abc123", 10, "application/json"),
|
|
new("OUTPUTS", "\\Logs\\app.log", "def456", 20, "text/plain")
|
|
});
|
|
|
|
var result = await _builder.BuildAsync(request, CancellationToken.None);
|
|
|
|
Assert.Collection(result.Manifest.Entries,
|
|
entry => Assert.Equal("inputs/config/env.json", entry.CanonicalPath),
|
|
entry => Assert.Equal("outputs/logs/app.log", entry.CanonicalPath));
|
|
}
|
|
|
|
private sealed class FakeRepository : IEvidenceBundleRepository
|
|
{
|
|
public EvidenceBundleId LastBundleId { get; private set; }
|
|
public TenantId LastTenantId { get; private set; }
|
|
public EvidenceBundleStatus LastStatus { get; private set; }
|
|
public string? LastRootHash { get; private set; }
|
|
public DateTimeOffset LastUpdatedAt { get; private set; }
|
|
|
|
public Task CreateBundleAsync(EvidenceBundle bundle, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task SetBundleAssemblyAsync(
|
|
EvidenceBundleId bundleId,
|
|
TenantId tenantId,
|
|
EvidenceBundleStatus status,
|
|
string rootHash,
|
|
DateTimeOffset updatedAt,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
LastBundleId = bundleId;
|
|
LastTenantId = tenantId;
|
|
LastStatus = status;
|
|
LastRootHash = rootHash;
|
|
LastUpdatedAt = updatedAt;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task MarkBundleSealedAsync(EvidenceBundleId bundleId, TenantId tenantId, EvidenceBundleStatus status, DateTimeOffset sealedAt, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task UpsertSignatureAsync(EvidenceBundleSignature signature, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task<EvidenceBundleDetails?> GetBundleAsync(EvidenceBundleId bundleId, TenantId tenantId, CancellationToken cancellationToken)
|
|
=> Task.FromResult<EvidenceBundleDetails?>(null);
|
|
|
|
public Task<bool> ExistsAsync(EvidenceBundleId bundleId, TenantId tenantId, CancellationToken cancellationToken)
|
|
=> Task.FromResult(true);
|
|
|
|
public Task<EvidenceHold> CreateHoldAsync(EvidenceHold hold, CancellationToken cancellationToken)
|
|
=> Task.FromResult(hold);
|
|
|
|
public Task ExtendBundleRetentionAsync(EvidenceBundleId bundleId, TenantId tenantId, DateTimeOffset? holdExpiresAt, DateTimeOffset processedAt, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task UpdateStorageKeyAsync(EvidenceBundleId bundleId, TenantId tenantId, string storageKey, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task UpdatePortableStorageKeyAsync(EvidenceBundleId bundleId, TenantId tenantId, string storageKey, DateTimeOffset generatedAt, CancellationToken cancellationToken)
|
|
=> Task.CompletedTask;
|
|
}
|
|
}
|