Skip to content
Open
6 changes: 3 additions & 3 deletions Tests/TestsClients/Blazor-WebAssembly/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@
await InvokeAsync(async () =>
{
IsConnected = true;
CurrentServerUrl = client.connection.GetUrl();
CurrentServerUrl = client.Url();
CurrentReconnectInfo = null;

SessionStartTime = DateTime.Now;
Expand Down Expand Up @@ -617,7 +617,7 @@
client.connection.OnTransaction += _onTransactionHandler;
client.connection.OnLedgerClosed += _onLedgerClosedHandler;

CurrentServerUrl = client.connection.GetUrl();
CurrentServerUrl = client.Url();
CurrentConnectionState = client.connection.CurrentConnectionState;

await base.OnInitializedAsync();
Expand All @@ -627,7 +627,7 @@
if (client.connection.IsConnected())
{
IsConnected = true;
CurrentServerUrl = client.connection.GetUrl();
CurrentServerUrl = client.Url();
CurrentConnectionState = client.connection.CurrentConnectionState;
StateHasChanged();
}
Expand Down
95 changes: 95 additions & 0 deletions Tests/Xrpl.Tests/Client/TestUHealthCheckOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using System;
using System.Threading.Tasks;

using Xrpl.Client;

using XrplTests;

namespace Xrpl.Tests
{
/// <summary>
/// Boundary checks for the health-check timing options. Both feed a timer directly —
/// <c>HealthCheckInterval</c> is cast to an int of milliseconds on the WASM path, where zero
/// fires once and never repeats and out-of-range values are rejected by the timer itself — so a
/// bad value has to fail on the way in, naming the option, rather than quietly disabling the
/// check that recovers dead connections.
/// </summary>
[TestClass]
public class TestUHealthCheckOptions
{
private static XrplClient CreateClient(TimeSpan? healthCheckInterval = null, TimeSpan? inactivityTimeout = null) =>
new XrplClient("ws://127.0.0.1:1", new XrplClient.ClientOptions
{
UseCustomPing = true,
UseCheckHealth = true,
HealthCheckInterval = healthCheckInterval ?? TimeSpan.FromSeconds(20),
InactivityTimeout = inactivityTimeout ?? TimeSpan.FromSeconds(60),
ConnectionAttemptTimeout = TimeSpan.FromSeconds(1),
ConnectionAcquisitionTimeout = TimeSpan.FromSeconds(1),
});

[TestMethod]
public void TestZeroHealthCheckIntervalIsRejected()
{
// Validation runs while the connection is being constructed, so the bad value is rejected
// before a client exists to connect with.
ArgumentException error = Helper.ThrowsException<ArgumentException>(
() => CreateClient(healthCheckInterval: TimeSpan.Zero));
StringAssert.Contains(error.Message, "HealthCheckInterval");
}

[TestMethod]
public void TestNegativeHealthCheckIntervalIsRejected()
{
ArgumentException error = Helper.ThrowsException<ArgumentException>(
() => CreateClient(healthCheckInterval: TimeSpan.FromMilliseconds(-1)));
StringAssert.Contains(error.Message, "HealthCheckInterval");
}

[TestMethod]
public void TestOutOfRangeHealthCheckIntervalIsRejected()
{
// Past int.MaxValue milliseconds - the WASM timer cannot represent it
ArgumentException error = Helper.ThrowsException<ArgumentException>(
() => CreateClient(healthCheckInterval: TimeSpan.FromDays(30)));
StringAssert.Contains(error.Message, "HealthCheckInterval");
}

[TestMethod]
public void TestNonPositiveInactivityTimeoutIsRejected()
{
ArgumentException error = Helper.ThrowsException<ArgumentException>(
() => CreateClient(inactivityTimeout: TimeSpan.Zero));
StringAssert.Contains(error.Message, "InactivityTimeout");
}

/// <summary>
/// The lower bound is 1ms, and the defaults must keep working — otherwise every existing
/// consumer would start failing on connect.
/// </summary>
[TestMethod]
public async Task TestBoundaryAndDefaultValuesAreAccepted()
{
// 1ms is the documented minimum: validation must let it through. Nothing is listening on
// port 1, so the connect attempt fails on the transport - not on config validation.
XrplClient atMinimum = CreateClient(
healthCheckInterval: TimeSpan.FromMilliseconds(1),
inactivityTimeout: TimeSpan.FromMilliseconds(1));

Exception minimumError = await Helper.ThrowsExceptionAsync<Exception>(() => atMinimum.Connect());
Assert.IsNotInstanceOfType(
minimumError,
typeof(ArgumentException),
$"1ms should pass validation, but connect failed with: {minimumError.Message}");

XrplClient atDefaults = CreateClient();
Exception defaultError = await Helper.ThrowsExceptionAsync<Exception>(() => atDefaults.Connect());
Assert.IsNotInstanceOfType(
defaultError,
typeof(ArgumentException),
$"The default options should pass validation, but connect failed with: {defaultError.Message}");
}
}
}
9 changes: 7 additions & 2 deletions Tests/Xrpl.Tests/Client/TestUOnConnectedHandlerFailure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,13 @@ public async Task TestRepeatedOnConnectedFailuresBackOff()
}
}

// ReconnectBaseDelay is 100ms and CalcBackoff doubles per attempt with 25% jitter,
// so the third gap is ~4x the first even at the extremes of the jitter range.
// CalcBackoff doubles per attempt off ReconnectBaseDelay (100ms), capped at
// ReconnectMaxDelay (1s), with 25% jitter. The handler-failure path seeds the counter
// with its consecutive-failure count, so the delays run 400ms, 800ms, then 1s (capped)
// — first to last is ~2.5x nominally, and still grows at the jitter extremes.
// This holds only while the configured cap stays above the earlier backoff values: with
// a cap at or below 400ms every gap would sit on the cap, and the comparison would come
// down to which way the jitter fell — a coin flip, not a stable result.
// Comparing first vs last rather than each consecutive pair keeps the assertion
// robust: what regressed before was a flat sequence, not the exact multiplier.
Assert.IsTrue(
Expand Down
8 changes: 8 additions & 0 deletions Tests/Xrpl.Tests/Client/TestUReconnectSessionRaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,14 @@ public async Task TestFailedConnectDuringReconnectLeavesLoopRunning()
}

// The server appears. Nobody touches the client from here on.
// The port was handed out before the awaits above, so check it is still free. StartMock
// binds on this thread, so a port taken meanwhile would come out as a raw SocketException
// from the line below; this turns it into a statement of the actual cause. Diagnostics,
// not a fix: the check itself binds and releases, so the port can still be lost between
// here and StartMock.
Assert.IsTrue(
TestUtils.IsPortStillFree(laterPort),
$"Port {laterPort} was taken by another process while the test held it — rerun.");
_secondRippled = StartMock(laterPort);

DateTime deadline = DateTime.UtcNow + TimeSpan.FromSeconds(40);
Expand Down
Loading
Loading