new advisories work and features gaps work
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
// <copyright file="EvidenceCardServiceTests.cs" company="StellaOps">
|
||||
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
|
||||
// Sprint: SPRINT_20260112_004_LB_evidence_card_core (EVPCARD-LB-004)
|
||||
// Description: Tests for EvidenceCardService
|
||||
// </copyright>
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Evidence.Pack;
|
||||
using StellaOps.Evidence.Pack.Models;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Evidence.Pack.Tests;
|
||||
|
||||
public sealed class EvidenceCardServiceTests
|
||||
{
|
||||
private readonly FixedGuidProvider _guidProvider = new(Guid.Parse("11111111-1111-1111-1111-111111111111"));
|
||||
private readonly TestTimeProvider _timeProvider = new(new DateTimeOffset(2026, 1, 14, 10, 0, 0, TimeSpan.Zero));
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateCardAsync_WithValidRequest_ReturnsCard()
|
||||
{
|
||||
var service = CreateService();
|
||||
var request = new EvidenceCardRequest
|
||||
{
|
||||
FindingId = "CVE-2024-12345",
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
ComponentPurl = "pkg:npm/lodash@4.17.21",
|
||||
TenantId = "tenant-1"
|
||||
};
|
||||
|
||||
var card = await service.CreateCardAsync(request);
|
||||
|
||||
Assert.NotNull(card);
|
||||
Assert.Equal("11111111111111111111111111111111", card.CardId);
|
||||
Assert.Equal("CVE-2024-12345", card.Subject.FindingId);
|
||||
Assert.Equal("sha256:abc123", card.Subject.ArtifactDigest);
|
||||
Assert.NotNull(card.Envelope);
|
||||
Assert.NotNull(card.SbomExcerpt);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateCardAsync_SetsGeneratedAtFromTimeProvider()
|
||||
{
|
||||
var service = CreateService();
|
||||
var request = new EvidenceCardRequest
|
||||
{
|
||||
FindingId = "CVE-2024-12345",
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
TenantId = "tenant-1"
|
||||
};
|
||||
|
||||
var card = await service.CreateCardAsync(request);
|
||||
|
||||
Assert.Equal(_timeProvider.GetUtcNow(), card.GeneratedAt);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task CreateCardAsync_WithComponentPurl_ExtractsComponentInfo()
|
||||
{
|
||||
var service = CreateService();
|
||||
var request = new EvidenceCardRequest
|
||||
{
|
||||
FindingId = "CVE-2024-12345",
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
ComponentPurl = "pkg:npm/lodash@4.17.21",
|
||||
TenantId = "tenant-1"
|
||||
};
|
||||
|
||||
var card = await service.CreateCardAsync(request);
|
||||
|
||||
Assert.Single(card.SbomExcerpt.Components);
|
||||
Assert.Equal("pkg:npm/lodash@4.17.21", card.SbomExcerpt.Components[0].Purl);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportCardAsync_Json_ReturnsValidJson()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var export = await service.ExportCardAsync(card, EvidenceCardExportFormat.Json);
|
||||
|
||||
Assert.Equal("application/json", export.ContentType);
|
||||
Assert.StartsWith("sha256:", export.ContentDigest);
|
||||
|
||||
var json = Encoding.UTF8.GetString(export.Content);
|
||||
using var document = JsonDocument.Parse(json);
|
||||
Assert.Equal(JsonValueKind.Object, document.RootElement.ValueKind);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportCardAsync_CompactJson_IsSmallerThanIndented()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var jsonExport = await service.ExportCardAsync(card, EvidenceCardExportFormat.Json);
|
||||
var compactExport = await service.ExportCardAsync(card, EvidenceCardExportFormat.CompactJson);
|
||||
|
||||
Assert.True(compactExport.Content.Length < jsonExport.Content.Length);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportCardAsync_CanonicalJson_IsDeterministic()
|
||||
{
|
||||
var service1 = CreateService();
|
||||
var service2 = CreateService();
|
||||
|
||||
var card1 = await CreateTestCard(service1);
|
||||
var card2 = await CreateTestCard(service2);
|
||||
|
||||
var export1 = await service1.ExportCardAsync(card1, EvidenceCardExportFormat.CanonicalJson);
|
||||
var export2 = await service2.ExportCardAsync(card2, EvidenceCardExportFormat.CanonicalJson);
|
||||
|
||||
Assert.Equal(export1.ContentDigest, export2.ContentDigest);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyCardAsync_ValidCard_ReturnsValid()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var result = await service.VerifyCardAsync(card);
|
||||
|
||||
Assert.True(result.Valid);
|
||||
Assert.True(result.SignatureValid);
|
||||
Assert.True(result.SbomDigestValid);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyCardAsync_WithMissingReceipt_AllowedByDefault()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var result = await service.VerifyCardAsync(card, new EvidenceCardVerificationOptions
|
||||
{
|
||||
AllowMissingReceipt = true
|
||||
});
|
||||
|
||||
Assert.True(result.Valid);
|
||||
Assert.Null(result.RekorReceiptValid);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyCardAsync_WithMissingReceipt_FailsWhenRequired()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var result = await service.VerifyCardAsync(card, new EvidenceCardVerificationOptions
|
||||
{
|
||||
AllowMissingReceipt = false
|
||||
});
|
||||
|
||||
Assert.False(result.Valid);
|
||||
Assert.Contains(result.Issues, i => i.Contains("Rekor receipt is required"));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyCardAsync_WithValidRekorReceipt_ReturnsTrue()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
// Add a valid-looking Rekor receipt
|
||||
var cardWithReceipt = card with
|
||||
{
|
||||
RekorReceipt = new RekorReceiptMetadata
|
||||
{
|
||||
Uuid = "abc123def456",
|
||||
LogIndex = 12345,
|
||||
LogId = "0x1234",
|
||||
LogUrl = "https://rekor.sigstore.dev",
|
||||
IntegratedTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
|
||||
RootHash = "sha256:root123",
|
||||
TreeSize = 100000,
|
||||
InclusionProofHashes = ImmutableArray.Create("hash1", "hash2"),
|
||||
CheckpointNote = "rekor.sigstore.dev - 12345\n100000\nroot123\n",
|
||||
CheckpointSignatures = ImmutableArray.Create(new CheckpointSignature
|
||||
{
|
||||
KeyId = "key1",
|
||||
Signature = "c2lnbmF0dXJl"
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
var result = await service.VerifyCardAsync(cardWithReceipt);
|
||||
|
||||
Assert.True(result.Valid);
|
||||
Assert.True(result.RekorReceiptValid);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task ExportCardAsync_SetsCorrectFileName()
|
||||
{
|
||||
var service = CreateService();
|
||||
var card = await CreateTestCard(service);
|
||||
|
||||
var export = await service.ExportCardAsync(card, EvidenceCardExportFormat.Json);
|
||||
|
||||
Assert.Equal($"evidence-card-{card.CardId}.json", export.FileName);
|
||||
}
|
||||
|
||||
private EvidenceCardService CreateService()
|
||||
{
|
||||
return new EvidenceCardService(
|
||||
_timeProvider,
|
||||
_guidProvider,
|
||||
NullLogger<EvidenceCardService>.Instance);
|
||||
}
|
||||
|
||||
private async Task<EvidenceCard> CreateTestCard(EvidenceCardService service)
|
||||
{
|
||||
var request = new EvidenceCardRequest
|
||||
{
|
||||
FindingId = "CVE-2024-12345",
|
||||
ArtifactDigest = "sha256:abc123",
|
||||
ComponentPurl = "pkg:npm/lodash@4.17.21",
|
||||
TenantId = "tenant-1"
|
||||
};
|
||||
|
||||
return await service.CreateCardAsync(request);
|
||||
}
|
||||
|
||||
private sealed class FixedGuidProvider : IGuidProvider
|
||||
{
|
||||
private readonly Guid _guid;
|
||||
|
||||
public FixedGuidProvider(Guid guid) => _guid = guid;
|
||||
|
||||
public Guid NewGuid() => _guid;
|
||||
}
|
||||
|
||||
private sealed class TestTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _fixedTime;
|
||||
|
||||
public TestTimeProvider(DateTimeOffset fixedTime) => _fixedTime = fixedTime;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _fixedTime;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user