Files
git.stella-ops.org/src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Tests/EvidenceBundleBuilderTests.cs
master 2eb6852d34
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Add unit tests for SBOM ingestion and transformation
- Implement `SbomIngestServiceCollectionExtensionsTests` to verify the SBOM ingestion pipeline exports snapshots correctly.
- Create `SbomIngestTransformerTests` to ensure the transformation produces expected nodes and edges, including deduplication of license nodes and normalization of timestamps.
- Add `SbomSnapshotExporterTests` to test the export functionality for manifest, adjacency, nodes, and edges.
- Introduce `VexOverlayTransformerTests` to validate the transformation of VEX nodes and edges.
- Set up project file for the test project with necessary dependencies and configurations.
- Include JSON fixture files for testing purposes.
2025-11-04 07:49:39 +02:00

128 lines
5.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.EvidenceLocker.Core.Builders;
using StellaOps.EvidenceLocker.Core.Domain;
using StellaOps.EvidenceLocker.Core.Repositories;
using StellaOps.EvidenceLocker.Infrastructure.Builders;
using Xunit;
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());
}
[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)));
}
[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;
}
}