sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -3,6 +3,8 @@
// </copyright>
using FluentAssertions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
namespace StellaOps.HybridLogicalClock.Tests;
@@ -14,6 +16,7 @@ namespace StellaOps.HybridLogicalClock.Tests;
public sealed class HybridLogicalClockTests
{
private const string TestNodeId = "test-node-1";
private static readonly ILogger<HybridLogicalClock> NullLogger = NullLogger<HybridLogicalClock>.Instance;
[Fact]
public void Tick_Monotonic_SuccessiveTicksAlwaysIncrease()
@@ -21,7 +24,7 @@ public sealed class HybridLogicalClockTests
// Arrange
var timeProvider = new FakeTimeProvider(DateTimeOffset.UtcNow);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Act
var timestamps = Enumerable.Range(0, 100)
@@ -43,7 +46,7 @@ public sealed class HybridLogicalClockTests
var fixedTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(fixedTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Act
var first = clock.Tick();
@@ -67,7 +70,7 @@ public sealed class HybridLogicalClockTests
var startTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(startTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Act - generate some ticks
clock.Tick();
@@ -90,7 +93,7 @@ public sealed class HybridLogicalClockTests
// Arrange
var timeProvider = new FakeTimeProvider();
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, "my-custom-node", stateStore);
var clock = new HybridLogicalClock(timeProvider, "my-custom-node", stateStore, NullLogger);
// Act
var timestamp = clock.Tick();
@@ -107,7 +110,7 @@ public sealed class HybridLogicalClockTests
var localTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(localTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Local tick first
var localTick = clock.Tick();
@@ -136,7 +139,7 @@ public sealed class HybridLogicalClockTests
var localTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(localTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Generate several local ticks to advance counter
clock.Tick();
@@ -166,7 +169,7 @@ public sealed class HybridLogicalClockTests
var localTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(localTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Local tick
clock.Tick();
@@ -197,7 +200,7 @@ public sealed class HybridLogicalClockTests
var timeProvider = new FakeTimeProvider(localTime);
var stateStore = new InMemoryHlcStateStore();
var maxSkew = TimeSpan.FromMinutes(1);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, maxSkew);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger, maxSkew);
// Remote timestamp is 2 minutes ahead (exceeds 1 minute tolerance)
var remote = new HlcTimestamp
@@ -213,7 +216,7 @@ public sealed class HybridLogicalClockTests
// Assert
act.Should().Throw<HlcClockSkewException>()
.Where(e => e.MaxAllowedSkew == maxSkew)
.Where(e => e.ObservedSkew > maxSkew);
.Where(e => e.ActualSkew > maxSkew);
}
[Fact]
@@ -222,7 +225,7 @@ public sealed class HybridLogicalClockTests
// Arrange
var timeProvider = new FakeTimeProvider();
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Act
var tick1 = clock.Tick();
@@ -237,107 +240,25 @@ public sealed class HybridLogicalClockTests
}
[Fact]
public async Task InitializeAsync_NoPersistedState_StartsFromCurrentTime()
public void Tick_PersistsStateToStore()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var startTime = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(startTime);
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
// Act
var recovered = await clock.InitializeAsync(ct);
// Assert
recovered.Should().BeFalse();
clock.Current.PhysicalTime.Should().Be(startTime.ToUnixTimeMilliseconds());
clock.Current.LogicalCounter.Should().Be(0);
}
[Fact]
public async Task InitializeAsync_WithPersistedState_ResumesFromPersisted()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var startTime = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(startTime);
var stateStore = new InMemoryHlcStateStore();
// Pre-persist state
var persistedState = new HlcTimestamp
{
PhysicalTime = startTime.ToUnixTimeMilliseconds(),
NodeId = TestNodeId,
LogicalCounter = 50
};
await stateStore.SaveAsync(persistedState, ct);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
// Act
var recovered = await clock.InitializeAsync(ct);
var firstTick = clock.Tick();
// Assert
recovered.Should().BeTrue();
firstTick.LogicalCounter.Should().BeGreaterThan(50); // Should continue from persisted + 1
}
[Fact]
public async Task InitializeAsync_PersistedStateOlderThanCurrent_UsesCurrentTime()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var startTime = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero);
var timeProvider = new FakeTimeProvider(startTime);
var stateStore = new InMemoryHlcStateStore();
// Pre-persist OLD state
var persistedState = new HlcTimestamp
{
PhysicalTime = startTime.AddHours(-1).ToUnixTimeMilliseconds(),
NodeId = TestNodeId,
LogicalCounter = 1000
};
await stateStore.SaveAsync(persistedState, ct);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
// Act
await clock.InitializeAsync(ct);
var firstTick = clock.Tick();
// Assert
// Should use current physical time since it's greater
firstTick.PhysicalTime.Should().Be(startTime.ToUnixTimeMilliseconds());
firstTick.LogicalCounter.Should().Be(1); // Reset because physical time advanced
}
[Fact]
public async Task Tick_PersistsState()
{
// Arrange
var ct = TestContext.Current.CancellationToken;
var timeProvider = new FakeTimeProvider();
var stateStore = new InMemoryHlcStateStore();
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore);
var clock = new HybridLogicalClock(timeProvider, TestNodeId, stateStore, NullLogger);
// Act
var tick = clock.Tick();
clock.Tick();
// Wait a bit for fire-and-forget persistence
await Task.Delay(50, ct);
// Assert
stateStore.Count.Should().Be(1);
// Assert - state should be persisted after tick
stateStore.GetAllStates().Count.Should().Be(1);
}
[Fact]
public void Constructor_NullTimeProvider_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new HybridLogicalClock(null!, TestNodeId, new InMemoryHlcStateStore());
var act = () => new HybridLogicalClock(null!, TestNodeId, new InMemoryHlcStateStore(), NullLogger);
// Assert
act.Should().Throw<ArgumentNullException>()
@@ -354,7 +275,8 @@ public sealed class HybridLogicalClockTests
var act = () => new HybridLogicalClock(
new FakeTimeProvider(),
nodeId!,
new InMemoryHlcStateStore());
new InMemoryHlcStateStore(),
NullLogger);
// Assert
act.Should().Throw<ArgumentException>();
@@ -367,10 +289,26 @@ public sealed class HybridLogicalClockTests
var act = () => new HybridLogicalClock(
new FakeTimeProvider(),
TestNodeId,
null!);
null!,
NullLogger);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("stateStore");
}
[Fact]
public void Constructor_NullLogger_ThrowsArgumentNullException()
{
// Arrange & Act
var act = () => new HybridLogicalClock(
new FakeTimeProvider(),
TestNodeId,
new InMemoryHlcStateStore(),
null!);
// Assert
act.Should().Throw<ArgumentNullException>()
.WithParameterName("logger");
}
}