Release QVPN 1.0.5 with Xray and resilient tunnel recovery

Integrate Xray 26.9.9 with the sing-box system tunnel and verify Windows routes during health checks.

Improve endpoint selection, network recovery, subscription refresh deadlines, diagnostics, packaging, and regression coverage.
This commit is contained in:
Курнат Андрей
2026-10-30 06:15:08 +03:00
parent ef622e6229
commit aae6dd28dd
34 changed files with 2603 additions and 120 deletions
@@ -0,0 +1,61 @@
using QVPN.Core;
internal static class SubscriptionRefreshPolicyTests
{
public static async Task RunAsync(Func<string, Action, Task> test)
{
var now = DateTimeOffset.Parse("2026-10-06T21:00:00Z");
await test("Service startup refreshes stale or undated subscription before health succeeds", () =>
{
var policy = new SubscriptionRefreshPolicy();
Check(policy.ShouldRefreshOnConnect(now.AddHours(-2), now));
Check(policy.ShouldRefreshOnConnect(null, now));
Check(policy.ShouldRefreshOnConnect(now.AddMinutes(-15), now));
Check(!policy.ShouldRefreshOnConnect(now.AddMinutes(-14), now));
});
await test("A new explicit connect checks current endpoint even with recently cached subscription", () =>
{
var policy = new SubscriptionRefreshPolicy();
Check(policy.ShouldRefreshOnConnect(now, now, explicitConnect: true));
policy.Attempted(now);
Check(!policy.ShouldRefreshOnConnect(now, now.AddSeconds(59), explicitConnect: true));
Check(policy.ShouldRefreshOnConnect(now, now.AddSeconds(60), explicitConnect: true));
});
await test("Endpoint outage refresh is allowed without a connected engine and limited to once per minute", () =>
{
var policy = new SubscriptionRefreshPolicy();
Check(policy.ShouldRefreshAfterEndpointFailure(now));
policy.Attempted(now);
for (int seconds = 0; seconds < 60; seconds++) Check(!policy.ShouldRefreshAfterEndpointFailure(now.AddSeconds(seconds)));
Check(policy.ShouldRefreshAfterEndpointFailure(now.AddSeconds(60)));
});
await test("Manual or routine subscription attempts also suppress immediate outage refresh loops", () =>
{
var policy = new SubscriptionRefreshPolicy();
policy.Attempted(now);
Check(!policy.ShouldRefreshAfterEndpointFailure(now.AddSeconds(1)));
Check(!policy.ShouldRefreshOnConnect(now.AddHours(-2), now.AddSeconds(1)));
policy.Attempted(now.AddSeconds(60));
Check(!policy.ShouldRefreshAfterEndpointFailure(now.AddSeconds(61)));
});
await test("A fresh successful subscription prevents automatic startup refresh after the retry cooldown", () =>
{
var policy = new SubscriptionRefreshPolicy();
policy.Attempted(now);
Check(!policy.ShouldRefreshOnConnect(now, now.AddMinutes(2)));
Check(policy.ShouldRefreshOnConnect(now, now.AddMinutes(15)));
});
await test("Clock rollback does not cause repeated automatic subscription refresh", () =>
{
var policy = new SubscriptionRefreshPolicy();
policy.Attempted(now);
Check(!policy.ShouldRefreshAfterEndpointFailure(now.AddMinutes(-5)));
Check(!policy.ShouldRefreshOnConnect(now.AddHours(-2), now.AddMinutes(-5), explicitConnect: true));
});
}
private static void Check(bool condition)
{
if (!condition) throw new InvalidOperationException("Subscription refresh policy regression.");
}
}