// // Copyright (c) StellaOps. Licensed under BUSL-1.1. // using FluentAssertions; using StellaOps.AirGap.Sync.Models; using Xunit; namespace StellaOps.AirGap.Sync.Tests; public sealed partial class HlcMergeServiceTests { [Fact] public async Task MergeAsync_TwoNodes_MergesByHlcOrderAsync() { var nodeA = CreateNodeLog("node-a", new[] { CreateEntry("node-a", 100, 0, Guid.Parse("aaaaaaaa-0001-0000-0000-000000000000")), CreateEntry("node-a", 102, 0, Guid.Parse("aaaaaaaa-0003-0000-0000-000000000000")) }); var nodeB = CreateNodeLog("node-b", new[] { CreateEntry("node-b", 101, 0, Guid.Parse("bbbbbbbb-0002-0000-0000-000000000000")), CreateEntry("node-b", 103, 0, Guid.Parse("bbbbbbbb-0004-0000-0000-000000000000")) }); var result = await _sut.MergeAsync(new[] { nodeA, nodeB }); result.MergedEntries.Should().HaveCount(4); result.MergedEntries[0].THlc.PhysicalTime.Should().Be(100); result.MergedEntries[1].THlc.PhysicalTime.Should().Be(101); result.MergedEntries[2].THlc.PhysicalTime.Should().Be(102); result.MergedEntries[3].THlc.PhysicalTime.Should().Be(103); result.SourceNodes.Should().HaveCount(2); } [Fact] public async Task MergeAsync_SamePhysicalTime_OrdersByLogicalCounterAsync() { var nodeA = CreateNodeLog("node-a", new[] { CreateEntry("node-a", 100, 0, Guid.Parse("aaaaaaaa-0000-0000-0000-000000000001")), CreateEntry("node-a", 100, 2, Guid.Parse("aaaaaaaa-0000-0000-0000-000000000003")) }); var nodeB = CreateNodeLog("node-b", new[] { CreateEntry("node-b", 100, 1, Guid.Parse("bbbbbbbb-0000-0000-0000-000000000002")), CreateEntry("node-b", 100, 3, Guid.Parse("bbbbbbbb-0000-0000-0000-000000000004")) }); var result = await _sut.MergeAsync(new[] { nodeA, nodeB }); result.MergedEntries.Should().HaveCount(4); result.MergedEntries[0].THlc.LogicalCounter.Should().Be(0); result.MergedEntries[1].THlc.LogicalCounter.Should().Be(1); result.MergedEntries[2].THlc.LogicalCounter.Should().Be(2); result.MergedEntries[3].THlc.LogicalCounter.Should().Be(3); } [Fact] public async Task MergeAsync_SameTimeAndCounter_OrdersByNodeIdAsync() { var nodeA = CreateNodeLog("alpha-node", new[] { CreateEntry("alpha-node", 100, 0, Guid.Parse("aaaaaaaa-0000-0000-0000-000000000001")) }); var nodeB = CreateNodeLog("beta-node", new[] { CreateEntry("beta-node", 100, 0, Guid.Parse("bbbbbbbb-0000-0000-0000-000000000002")) }); var result = await _sut.MergeAsync(new[] { nodeA, nodeB }); result.MergedEntries.Should().HaveCount(2); result.MergedEntries[0].SourceNodeId.Should().Be("alpha-node"); result.MergedEntries[1].SourceNodeId.Should().Be("beta-node"); } [Fact] public async Task MergeAsync_RecomputesUnifiedChainAsync() { var nodeLog = CreateNodeLog("node-a", new[] { CreateEntry("node-a", 100, 0, Guid.Parse("11111111-1111-1111-1111-111111111111")), CreateEntry("node-a", 200, 0, Guid.Parse("22222222-2222-2222-2222-222222222222")) }); var result = await _sut.MergeAsync(new[] { nodeLog }); result.MergedEntries.Should().HaveCount(2); result.MergedEntries[0].MergedLink.Should().NotBeNull(); result.MergedEntries[1].MergedLink.Should().NotBeNull(); result.MergedChainHead.Should().NotBeNull(); result.MergedEntries[0].MergedLink.Should().HaveCount(32); result.MergedChainHead.Should().BeEquivalentTo(result.MergedEntries[1].MergedLink); } }