using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using PolyTrader.Web.Updates;
using Xunit;
namespace PolyTrader.Tests.WebUi
{
///
/// Update-Status im WebUI (#565): Zwischenspeicher, Mindestabstand für "Jetzt prüfen", Anzeige.
///
public class UpdateStatusTests
{
private sealed class FakeChecker : IUpdateChecker
{
private readonly Func _now;
public int Calls;
public UpdateCheckSnapshot Result { get; set; }
public FakeChecker(Func now)
{
_now = now;
Result = new UpdateCheckSnapshot(DateTime.MinValue, true, true, false, "0.2.0", "Neu: Log-Viewer", true, null);
}
public async Task CheckAsync(CancellationToken ct)
{
Interlocked.Increment(ref Calls);
await Task.Delay(20, ct);
return Result with { CheckedAtUtc = _now() };
}
}
private sealed class Clock
{
public DateTime Now = new(2026, 9, 13, 12, 0, 0, DateTimeKind.Utc);
}
[Fact]
public async Task Innerhalb_der_Haltezeit_wird_nicht_erneut_gefragt()
{
var clock = new Clock();
var checker = new FakeChecker(() => clock.Now);
var service = new UpdateStatusService(checker, () => clock.Now);
var first = await service.GetAsync(force: false, CancellationToken.None);
clock.Now = clock.Now.AddMinutes(29);
var second = await service.GetAsync(force: false, CancellationToken.None);
Assert.False(first.FromCache);
Assert.True(second.FromCache);
Assert.Equal(1, checker.Calls);
}
[Fact]
public async Task Nach_Ablauf_der_Haltezeit_wird_frisch_gefragt()
{
var clock = new Clock();
var checker = new FakeChecker(() => clock.Now);
var service = new UpdateStatusService(checker, () => clock.Now);
await service.GetAsync(false, CancellationToken.None);
clock.Now = clock.Now + UpdateStatusService.CacheDuration;
var again = await service.GetAsync(false, CancellationToken.None);
Assert.False(again.FromCache);
Assert.Equal(2, checker.Calls);
}
[Fact]
public async Task Jetzt_pruefen_zu_frueh_liefert_den_vorigen_Stand_mit_Wartezeit()
{
var clock = new Clock();
var checker = new FakeChecker(() => clock.Now);
var service = new UpdateStatusService(checker, () => clock.Now);
await service.GetAsync(false, CancellationToken.None);
clock.Now = clock.Now.AddSeconds(20);
var tooEarly = await service.GetAsync(force: true, CancellationToken.None);
Assert.True(tooEarly.FromCache);
Assert.Equal(TimeSpan.FromSeconds(40), tooEarly.RetryAfter);
Assert.Equal(1, checker.Calls);
clock.Now = clock.Now.AddSeconds(40);
var allowed = await service.GetAsync(force: true, CancellationToken.None);
Assert.False(allowed.FromCache);
Assert.Null(allowed.RetryAfter);
Assert.Equal(2, checker.Calls);
}
[Fact]
public async Task Gleichzeitige_Aufrufe_fragen_nur_einmal()
{
var clock = new Clock();
var checker = new FakeChecker(() => clock.Now);
var service = new UpdateStatusService(checker, () => clock.Now);
await Task.WhenAll(Enumerable.Range(0, 10).Select(_ => service.GetAsync(false, CancellationToken.None)));
Assert.Equal(1, checker.Calls);
}
[Fact]
public async Task Route_liefert_Fassung_Stand_und_verweist_zur_Installation_auf_den_Desktop()
{
var clock = new Clock();
var checker = new FakeChecker(() => clock.Now);
await using var web = await WebUiTestHost.StartAsync(s =>
s.AddSingleton(new UpdateStatusService(checker, () => clock.Now)));
var status = await web.GetJsonAsync("/api/updates");
Assert.Equal(PolyTrader.Core.BuildInfo.Version, status.GetProperty("currentVersion").GetString());
Assert.True(status.GetProperty("updateAvailable").GetBoolean());
Assert.Equal("0.2.0", status.GetProperty("latestVersion").GetString());
Assert.True(status.GetProperty("isCritical").GetBoolean());
Assert.Equal("desktop", status.GetProperty("installVia").GetString());
}
[Fact]
public async Task Ohne_Dienst_503()
{
await using var web = await WebUiTestHost.StartAsync();
using var res = await web.Client.GetAsync("/api/updates");
Assert.Equal(503, (int)res.StatusCode);
}
[Fact]
public async Task Das_WebUI_kennt_keinen_Weg_zum_Installieren()
{
await using var web = await WebUiTestHost.StartAsync();
var routes = web.Get().Endpoints
.OfType()
.Select(e => e.RoutePattern.RawText ?? string.Empty)
.Where(r => r.Contains("update", StringComparison.OrdinalIgnoreCase))
.ToList();
Assert.Equal(new[] { "/api/updates" }, routes);
}
}
}