Files
Just.Core/Core.Tests/SeqIdTests/NextId.cs
just 3665abaab8
All checks were successful
.NET Test / test (push) Successful in 49s
.NET Publish / publish (push) Successful in 41s
dotnet 9 and sequential id
2025-08-01 22:21:42 +04:00

140 lines
4.1 KiB
C#

namespace Just.Core.Tests.SeqIdTests;
public class NextId
{
private static readonly DateTime TestEpoch = new(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private readonly SeqId _seqId = new(TestEpoch);
private const int TimestampShift = 22; // 63 - 41 = 22
private const long TimestampMask = 0x1FFFFFFFFFF; // 41 bits mask
private const int SeqShift = 14; // TimestampShift - 8 = 14
private const long SeqMask = 0xFF; // 8 bits mask
private const long RandMask = 0x3FFF; // 14 bits mask (since 2^14 = 16384)
[Fact]
public void NextId_ShouldHaveCorrectBitStructure()
{
// Arrange
var time = TestEpoch.AddMilliseconds(500);
// Act
long id = _seqId.Next(time);
// Assert
long timestampPart = (id >> TimestampShift) & TimestampMask;
long sequencePart = (id >> SeqShift) & SeqMask;
long randomPart = id & RandMask;
timestampPart.Should().Be(500);
sequencePart.Should().Be(0);
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void NextId_ShouldIncrementSequenceForSameTimestamp()
{
// Arrange
var time = TestEpoch.AddMilliseconds(100);
// Act
long id1 = _seqId.Next(time);
long id2 = _seqId.Next(time);
// Assert
long sequence1 = (id1 >> SeqShift) & SeqMask;
long sequence2 = (id2 >> SeqShift) & SeqMask;
sequence2.Should().Be(sequence1 + 1);
}
[Fact]
public void NextId_ShouldResetSequenceForNewTimestamp()
{
// Arrange
var time1 = TestEpoch.AddMilliseconds(100);
var time2 = time1.AddMilliseconds(1);
// Act
_ = _seqId.Next(time1); // Sequence increments to 0
_ = _seqId.Next(time1); // Sequence increments to 1
long id = _seqId.Next(time2); // Should reset to 0
// Assert
long sequence = (id >> SeqShift) & SeqMask;
sequence.Should().Be(0);
}
[Fact]
public void NextId_ShouldThrowWhenTimestampDecreases()
{
// Arrange
var time1 = TestEpoch.AddMilliseconds(200);
var time2 = TestEpoch.AddMilliseconds(100);
// Act & Assert
_seqId.Next(time1); // First call sets last timestamp
Action act = () => _seqId.Next(time2);
act.Should().Throw<InvalidOperationException>()
.WithMessage("Refused to create new SeqId. Last timestamp is in the future.");
}
[Fact]
public void NextId_ShouldThrowWhenSequenceExhausted()
{
// Arrange
var time = TestEpoch.AddMilliseconds(200);
// Act & Assert
for (int i = 0; i < 255; i++)
{
_seqId.Next(time); // Exhauste sequence
}
Action act = () => _seqId.Next(time);
act.Should().Throw<IndexOutOfRangeException>()
.WithMessage("Refused to create new SeqId. Sequence exhausted.");
}
[Fact]
public void NextId_WithStrongEntropy_ShouldSetLower14Bits()
{
// Arrange
var time = TestEpoch.AddMilliseconds(300);
// Act
long id = _seqId.Next(time, RngEntropy.Strong);
// Assert
long randomPart = id & RandMask;
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void NextId_WithWeakEntropy_ShouldSetLower14Bits()
{
// Arrange
var time = TestEpoch.AddMilliseconds(400);
// Act
long id = _seqId.Next(time, RngEntropy.Weak);
// Assert
long randomPart = id & RandMask;
randomPart.Should().BeInRange(0, RandMask);
}
[Fact]
public void DefaultInstance_NextId_ShouldUseDefaultEpoch()
{
// Arrange
var now = DateTime.UtcNow;
var defaultEpoch = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long expectedTimestamp = (long)(now - defaultEpoch).TotalMilliseconds;
// Act
long id = SeqId.NextId();
// Assert
long timestampPart = (id >> TimestampShift) & TimestampMask;
timestampPart.Should().BeCloseTo(expectedTimestamp & TimestampMask, 1); // Mask handles overflow
}
}