Files
MoShiro/backend/fleet/tests/Fleet.Tests/CoreTests.cs
T
2026-10-04 21:46:39 +08:00

124 lines
4.0 KiB
C#

using Fleet.Core;
namespace Fleet.Tests;
public sealed class CoreTests
{
[Theory]
[InlineData(0UL)]
[InlineData(1UL)]
[InlineData(127UL)]
[InlineData(128UL)]
[InlineData(300UL)]
[InlineData(ulong.MaxValue)]
public void VarInt_RoundTrips(ulong value)
{
Span<byte> buffer = stackalloc byte[VarInt.MaxBytes];
int written = VarInt.Write(buffer, value);
Assert.True(VarInt.TryRead(buffer[..written], out ulong read, out int consumed));
Assert.Equal(value, read);
Assert.Equal(written, consumed);
}
[Fact]
public void VarInt_RejectsTruncatedAndOverlongInput()
{
Assert.False(VarInt.TryRead([0x80], out _, out _));
Assert.False(VarInt.TryRead([], out _, out _));
byte[] overlong = [.. Enumerable.Repeat((byte)0x80, 11), 0x01];
Assert.False(VarInt.TryRead(overlong, out _, out _));
}
[Theory]
[InlineData(0L)]
[InlineData(-1L)]
[InlineData(1L)]
[InlineData(long.MinValue)]
[InlineData(long.MaxValue)]
public void ZigZag_RoundTrips_AndKeepsSmallMagnitudesSmall(long value)
{
Assert.Equal(value, VarInt.UnZigZag(VarInt.ZigZag(value)));
if (value is >= -63 and <= 63)
{
Assert.True(VarInt.ZigZag(value) < 128);
}
}
[Fact]
public void Ulid_IsTwentySixCharacters_SortsByTime_AndDoesNotRepeat()
{
var time = new FakeClock(new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero));
string a = Ulid.New(time);
time.Advance(TimeSpan.FromMilliseconds(5));
string b = Ulid.New(time);
Assert.Equal(26, a.Length);
Assert.True(string.CompareOrdinal(a, b) < 0);
Assert.Equal(1000, Enumerable.Range(0, 1000).Select(_ => Ulid.New(time)).Distinct().Count());
Assert.StartsWith("agt_", Ulid.NewPrefixed("agt", time));
}
[Fact]
public void Tokens_AreHighEntropy_AndOnlyTheHashIsComparable()
{
string a = TokenHash.NewToken("jt");
string b = TokenHash.NewToken("jt");
Assert.NotEqual(a, b);
Assert.StartsWith("jt_", a);
Assert.Equal(3 + 43, a.Length);
Assert.True(TokenHash.FixedTimeEquals(TokenHash.Sha256(a), TokenHash.Sha256(a)));
Assert.False(TokenHash.FixedTimeEquals(TokenHash.Sha256(a), TokenHash.Sha256(b)));
}
[Fact]
public void Base64Url_RoundTrips_AndRejectsGarbage()
{
byte[] data = [0xFB, 0xFF, 0xFE, 0x00, 0x01];
string text = Base64Url.Encode(data);
Assert.DoesNotContain('+', text);
Assert.DoesNotContain('/', text);
Assert.DoesNotContain('=', text);
Assert.True(Base64Url.TryDecode(text, out byte[] back));
Assert.Equal(data, back);
Assert.False(Base64Url.TryDecode("***", out _));
}
[Fact]
public void Result_CarriesEitherAValueOrAnError_AndRefusesTheWrongAccess()
{
Result<int> ok = Result<int>.Ok(5);
Result<int> failed = Result<int>.Fail(FleetErrors.NotFound, "missing");
Assert.True(ok.IsSuccess);
Assert.Equal(10, ok.Map(v => v * 2).Value);
Assert.False(failed.IsSuccess);
Assert.Equal(FleetErrors.NotFound, failed.Error.Code);
Assert.Throws<InvalidOperationException>(() => failed.Value);
Assert.Throws<InvalidOperationException>(() => ok.Error);
Assert.Equal("fleet_not_found", failed.Map(v => v).Error.Code.Value);
}
[Fact]
public void FrameworkErrorCodes_ArePrefixedAndSnakeCase()
{
foreach (System.Reflection.FieldInfo field in typeof(FleetErrors).GetFields())
{
string value = ((ErrorCode)field.GetValue(null)!).Value;
Assert.StartsWith("fleet_", value);
Assert.Matches("^[a-z]+(_[a-z]+)*$", value);
}
}
private sealed class FakeClock : TimeProvider
{
private DateTimeOffset _now;
public FakeClock(DateTimeOffset now)
{
_now = now;
}
public override DateTimeOffset GetUtcNow() => _now;
public void Advance(TimeSpan by) => _now += by;
}
}