using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Time.Testing; using StellaOps.Concelier.Merge.Services; using StellaOps.Concelier.Models; using StellaOps.Concelier.Storage.MergeEvents; namespace StellaOps.Concelier.Merge.Tests; public sealed class MergeEventWriterTests { [Fact] public async Task AppendAsync_WritesRecordWithComputedHashes() { var store = new InMemoryMergeEventStore(); var calculator = new CanonicalHashCalculator(); var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2024-05-01T00:00:00Z")); var writer = new MergeEventWriter(store, calculator, timeProvider, NullLogger.Instance); var before = CreateAdvisory("CVE-2024-0001", "Initial"); var after = CreateAdvisory("CVE-2024-0001", "Sample", summary: "Updated"); var documentIds = new[] { Guid.NewGuid(), Guid.NewGuid() }; var record = await writer.AppendAsync("CVE-2024-0001", before, after, documentIds, Array.Empty(), CancellationToken.None); Assert.NotEqual(Guid.Empty, record.Id); Assert.Equal("CVE-2024-0001", record.AdvisoryKey); Assert.True(record.AfterHash.Length > 0); Assert.Equal(timeProvider.GetUtcNow(), record.MergedAt); Assert.Equal(documentIds, record.InputDocumentIds); Assert.NotNull(store.LastRecord); Assert.Same(store.LastRecord, record); } [Fact] public async Task AppendAsync_NullBeforeUsesEmptyHash() { var store = new InMemoryMergeEventStore(); var calculator = new CanonicalHashCalculator(); var timeProvider = new FakeTimeProvider(DateTimeOffset.Parse("2024-05-01T00:00:00Z")); var writer = new MergeEventWriter(store, calculator, timeProvider, NullLogger.Instance); var after = CreateAdvisory("CVE-2024-0002", "Changed"); var record = await writer.AppendAsync("CVE-2024-0002", null, after, Array.Empty(), Array.Empty(), CancellationToken.None); Assert.Empty(record.BeforeHash); Assert.True(record.AfterHash.Length > 0); } private static Advisory CreateAdvisory(string advisoryKey, string title, string? summary = null) { return new Advisory( advisoryKey, title, summary, language: "en", published: DateTimeOffset.Parse("2024-01-01T00:00:00Z"), modified: DateTimeOffset.Parse("2024-01-02T00:00:00Z"), severity: "medium", exploitKnown: false, aliases: new[] { advisoryKey }, references: new[] { new AdvisoryReference("https://example.com/" + advisoryKey.ToLowerInvariant(), "external", "vendor", summary: null, provenance: AdvisoryProvenance.Empty) }, affectedPackages: Array.Empty(), cvssMetrics: Array.Empty(), provenance: Array.Empty()); } private sealed class InMemoryMergeEventStore : IMergeEventStore { public MergeEventRecord? LastRecord { get; private set; } public Task AppendAsync(MergeEventRecord record, CancellationToken cancellationToken) { LastRecord = record; return Task.CompletedTask; } public Task> GetRecentAsync(string advisoryKey, int limit, CancellationToken cancellationToken) => Task.FromResult>(Array.Empty()); } }