Files
Just.Core/Core.Tests/GuidV8Tests/ExtractTimestamp.cs
T
just 8e961352d2
.NET Test / .NET tests (push) Successful in 1m55s
fixed seqid test, added timestamp extraction
2026-07-10 22:06:37 +04:00

142 lines
4.6 KiB
C#

namespace Just.Core.Tests.GuidV8Tests;
public class ExtractTimestamp
{
[Fact]
public void RoundTrip_ShouldReturnOriginalTimestamp()
{
var original = new DateTime(2026, 7, 10, 21, 30, 0, DateTimeKind.Utc);
var guid = GuidV8.NewGuid(original, RngEntropy.Weak);
var extracted = GuidV8.ExtractTimestamp(guid);
extracted.ShouldBe(original);
extracted.Kind.ShouldBe(DateTimeKind.Utc);
}
[Fact]
public void RoundTrip_ShouldPreserve100MicrosecondPrecision()
{
// 100-microsecond precision: 1234567 * 100µs = 123456.7ms
var original = new DateTime(2024, 3, 15, 8, 45, 30, 123, DateTimeKind.Utc)
.AddTicks(4567); // sub-millisecond ticks
var guid = GuidV8.NewGuid(original, RngEntropy.Weak);
var extracted = GuidV8.ExtractTimestamp(guid);
// 123ms + 4567 ticks = 123ms + 456.7µs
// timestamp = (ticks since epoch) / 1000 (100µs units)
// Ticks: 123ms = 1,230,000 ticks; + 4,567 ticks = 1,234,567 ticks
// timestamp = 1,234,567 / 1000 = 1234 (truncates last 3 digits)
// recovered = 1234 * 1000 = 1,234,000 ticks = 123.4ms
// So the sub-100µs part (67 ticks) is lost
extracted.ShouldBe(new DateTime(2024, 3, 15, 8, 45, 30, 123, DateTimeKind.Utc)
.AddTicks(4000)); // 123.4ms
}
[Fact]
public void RoundTrip_UnixEpoch_ShouldReturnExactEpoch()
{
var original = DateTime.UnixEpoch;
var guid = GuidV8.NewGuid(original, RngEntropy.Weak);
var extracted = GuidV8.ExtractTimestamp(guid);
extracted.ShouldBe(original);
}
[Fact]
public void RoundTrip_PreUnixEpoch_ShouldThrowArgumentException()
{
// Negative timestamps cannot be encoded correctly because the uint casts
// in NewGuid corrupt the sign. The encoding should reject them.
var preEpoch = new DateTime(1969, 7, 20, 20, 17, 40, DateTimeKind.Utc);
Action act = () => GuidV8.NewGuid(preEpoch);
act.ShouldThrow<ArgumentException>();
}
[Fact]
public void RoundTrip_FarFuture_ShouldPreserveTimestamp()
{
// Year 2200 — well within 48-bit range
var original = new DateTime(2200, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var guid = GuidV8.NewGuid(original, RngEntropy.Weak);
var extracted = GuidV8.ExtractTimestamp(guid);
extracted.ShouldBe(original);
}
[Fact]
public void ExtractTimestamp_WithStrongEntropy_ShouldStillRecoverTimestamp()
{
var original = new DateTime(2025, 12, 25, 10, 0, 0, DateTimeKind.Utc);
var guid = GuidV8.NewGuid(original, RngEntropy.Strong);
var extracted = GuidV8.ExtractTimestamp(guid);
extracted.ShouldBe(original);
}
[Fact]
public void NonV8Guid_ShouldThrowArgumentException()
{
var guid = Guid.NewGuid(); // UUID v4
Action act = () => GuidV8.ExtractTimestamp(guid);
act.ShouldThrow<ArgumentException>()
.WithMessage("The provided GUID is not a UUID v8 (version=4). (Parameter 'guid')");
}
[Fact]
public void GuidEmpty_ShouldThrowArgumentException()
{
Action act = () => GuidV8.ExtractTimestamp(Guid.Empty);
act.ShouldThrow<ArgumentException>()
.WithMessage("The provided GUID is not a UUID v8 (version=0). (Parameter 'guid')");
}
[Fact]
public void MultipleRoundTrips_ShouldAllMatch()
{
var baseTime = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc);
for (int i = 0; i < 100; i++)
{
var original = baseTime.AddMinutes(i * 37 + 13); // varied intervals
var guid = GuidV8.NewGuid(original, RngEntropy.Weak);
var extracted = GuidV8.ExtractTimestamp(guid);
extracted.ShouldBe(original);
}
}
[Fact]
public void ExtractTimestamp_ThenNewGuid_ShouldProduceSortableSequence()
{
var t1 = new DateTime(2026, 5, 1, 0, 0, 0, DateTimeKind.Utc);
var t2 = t1.AddSeconds(1);
var t3 = t2.AddSeconds(1);
var g1 = GuidV8.NewGuid(t1, RngEntropy.Weak);
var g2 = GuidV8.NewGuid(t2, RngEntropy.Weak);
var g3 = GuidV8.NewGuid(t3, RngEntropy.Weak);
var extracted1 = GuidV8.ExtractTimestamp(g1);
var extracted2 = GuidV8.ExtractTimestamp(g2);
var extracted3 = GuidV8.ExtractTimestamp(g3);
extracted1.ShouldBe(t1);
extracted2.ShouldBe(t2);
extracted3.ShouldBe(t3);
// Verify they sort in order
new[] { g3, g1, g2 }.Order().ShouldBe([g1, g2, g3]);
new[] { extracted3, extracted1, extracted2 }.Order().ShouldBe([t1, t2, t3]);
}
}