Minecraft-Console-Client/MinecraftClient.Tests/AutoRelogRetryPolicyTests.cs

146 lines
4.5 KiB
C#
Raw Permalink Normal View History

2026-07-19 14:42:10 +02:00
using MinecraftClient.ChatBots;
using MinecraftClient.Scripting;
namespace MinecraftClient.Tests;
public sealed class AutoRelogRetryPolicyTests
{
[Fact]
public void DefaultConfigurationUsesUnlimitedRetries()
{
Assert.Equal(-1, new AutoRelog.Configs().Retries);
}
[Fact]
public void UnlimitedRetriesNeverExhaust()
{
var policy = new AutoRelogRetryPolicy(new ManualTimeProvider());
for (int attempt = 1; attempt <= 100; attempt++)
{
Assert.True(policy.TryReserveAttempt(-1, out int retriesLeft));
Assert.Equal(-1, retriesLeft);
Assert.Equal(attempt, policy.Attempts);
}
}
[Fact]
public void ZeroRetriesDisablesReconnect()
{
var policy = new AutoRelogRetryPolicy(new ManualTimeProvider());
Assert.False(policy.TryReserveAttempt(0, out int retriesLeft));
Assert.Equal(0, retriesLeft);
Assert.Equal(0, policy.Attempts);
}
[Fact]
public void FiniteRetryLimitIsExact()
{
var policy = new AutoRelogRetryPolicy(new ManualTimeProvider());
Assert.True(policy.TryReserveAttempt(3, out int firstRetriesLeft));
Assert.True(policy.TryReserveAttempt(3, out int secondRetriesLeft));
Assert.True(policy.TryReserveAttempt(3, out int thirdRetriesLeft));
Assert.False(policy.TryReserveAttempt(3, out int exhaustedRetriesLeft));
Assert.Equal(2, firstRetriesLeft);
Assert.Equal(1, secondRetriesLeft);
Assert.Equal(0, thirdRetriesLeft);
Assert.Equal(0, exhaustedRetriesLeft);
}
[Fact]
public void RejectedRestartDoesNotConsumeRetry()
{
var policy = new AutoRelogRetryPolicy(new ManualTimeProvider());
Assert.True(policy.TryReserveAttempt(1, out _));
policy.RollBackReservedAttempt();
Assert.True(policy.TryReserveAttempt(1, out int retriesLeft));
Assert.Equal(0, retriesLeft);
}
[Fact]
public void StableConnectionResetsRetryBudget()
{
var timeProvider = new ManualTimeProvider();
var policy = new AutoRelogRetryPolicy(timeProvider);
Assert.True(policy.TryReserveAttempt(1, out _));
policy.MarkJoined();
timeProvider.Advance(AutoRelogRetryPolicy.StableConnectionThreshold - TimeSpan.FromMilliseconds(1));
Assert.False(policy.ResetAfterStableConnection());
timeProvider.Advance(TimeSpan.FromMilliseconds(1));
Assert.True(policy.ResetAfterStableConnection());
Assert.Equal(0, policy.Attempts);
Assert.True(policy.TryReserveAttempt(1, out _));
}
[Fact]
public void TransportLossAlwaysReconnectsWhenEnabled()
{
bool reconnect = AutoRelogRetryPolicy.ShouldReconnect(
ChatBot.DisconnectReason.ConnectionLost,
"A transport-specific error without a configured phrase",
ignoreKickMessage: false,
["Server is restarting"],
out string? matchedMessage);
Assert.True(reconnect);
Assert.Null(matchedMessage);
}
[Theory]
[InlineData(ChatBot.DisconnectReason.InGameKick)]
[InlineData(ChatBot.DisconnectReason.LoginRejected)]
public void ServerMessageMatchingIsCaseInsensitive(ChatBot.DisconnectReason reason)
{
bool reconnect = AutoRelogRetryPolicy.ShouldReconnect(
reason,
"THE SERVER IS RESTARTING NOW",
ignoreKickMessage: false,
["server is restarting"],
out string? matchedMessage);
Assert.True(reconnect);
Assert.Equal("server is restarting", matchedMessage);
}
[Fact]
public void UserLogoutNeverReconnects()
{
Assert.False(AutoRelogRetryPolicy.ShouldReconnect(
ChatBot.DisconnectReason.UserLogout,
"Server is restarting",
ignoreKickMessage: true,
["Server is restarting"],
out _));
}
[Fact]
public void IgnoreKickMessageAllowsNonmatchingServerKick()
{
Assert.True(AutoRelogRetryPolicy.ShouldReconnect(
ChatBot.DisconnectReason.InGameKick,
"Administrative removal",
ignoreKickMessage: true,
[],
out _));
}
private sealed class ManualTimeProvider : TimeProvider
{
private DateTimeOffset utcNow = DateTimeOffset.UnixEpoch;
public override DateTimeOffset GetUtcNow() => utcNow;
internal void Advance(TimeSpan duration)
{
utcNow += duration;
}
}
}