using AvParser.Core.Collecting; using AvParser.Core.Proxies; using AvParser.Infrastructure.Storage; using AvParser.UI.Tests.Fakes; using AvParser.UI.ViewModels; using ReactiveUI.Primitives.Concurrency; namespace AvParser.UI.Tests; public class DashboardViewModelTests { private sealed class TestPaths : IAppPaths { public string DataDirectory => Path.Combine(Path.GetTempPath(), "AvParserTests"); public string SettingsFile => Path.Combine(DataDirectory, "settings.json"); public string CustomProxiesFile => Path.Combine(DataDirectory, "proxies.custom.json"); public string ProxyStateFile => Path.Combine(DataDirectory, "proxies.state.json"); public string LogDirectory => Path.Combine(DataDirectory, "logs"); } private sealed class EmptyServiceProvider : IServiceProvider { public object? GetService(Type serviceType) => null; } private sealed class EmptyUserSourceStore : IUserSourceStore { public event EventHandler? Changed { add { } remove { } } public IReadOnlyList List() => []; public Task AddAsync( AvParser.Core.Collecting.Sources.PatternSourceConfig config, CancellationToken cancellationToken = default ) => Task.FromResult(config); public Task UpdateAsync( AvParser.Core.Collecting.Sources.PatternSourceConfig config, CancellationToken cancellationToken = default ) => Task.FromResult(false); public Task RemoveAsync(string id, CancellationToken cancellationToken = default) => Task.FromResult(false); } private static DashboardViewModel Build(IProxyPool pool) => new( new MediaSourceCatalog(new EmptyUserSourceStore()), new TestPaths(), pool, new EmptyServiceProvider(), ImmediateSequencer.Instance ); private static ProxyEndpoint Endpoint(string host) => new(ProxyProtocol.Http, host, 8080); [Fact] public void An_empty_pool_reports_nothing_rather_than_zeroes_dressed_up_as_health() { using var page = Build(new ProxyPool([], new FakeProxyProbe(), new ProxyOptions())); page.ProxyTotal.ShouldBe(0); page.HasProxies.ShouldBeFalse(); page.ProxyLatency.ShouldBeNull(); page.ProxyRequests.ShouldBeNull(); } [Fact] public async Task The_pool_summary_counts_live_and_unchecked_entries() { var alive = Endpoint("1.2.3.4"); var dead = Endpoint("5.6.7.8"); var pool = new ProxyPool( [new FakeProxySource([alive, dead])], new FakeProxyProbe().Set(alive, alive: true), new ProxyOptions() ); using var page = Build(pool); await pool.RefreshAsync(TestContext.Current.CancellationToken); page.RefreshProxyStats(); page.ProxyTotal.ShouldBe(2); page.HasProxies.ShouldBeTrue(); // Nothing has been probed yet, so nothing is live however promising the list looks. page.ProxyLive.ShouldBe(0); page.ProxyUnchecked.ShouldBe(2); await pool.SweepAsync(cancellationToken: TestContext.Current.CancellationToken); page.RefreshProxyStats(); page.ProxyLive.ShouldBe(1); page.ProxyUnchecked.ShouldBe(0); page.ProxyLatency.ShouldNotBeNull().ShouldContain("20"); } [Fact] public async Task Real_requests_show_up_as_a_success_rate() { var endpoint = Endpoint("1.2.3.4"); var pool = new ProxyPool( [new FakeProxySource([endpoint])], new FakeProxyProbe().Set(endpoint, alive: true), new ProxyOptions() ); using var page = Build(pool); await pool.RefreshAsync(TestContext.Current.CancellationToken); await pool.SweepAsync(cancellationToken: TestContext.Current.CancellationToken); using (var lease = await pool.AcquireAsync(TestContext.Current.CancellationToken)) { lease.ShouldNotBeNull().ReportSuccess(TimeSpan.FromMilliseconds(30)); } page.RefreshProxyStats(); page.ProxyRequests.ShouldNotBeNull().ShouldContain("1 of 1"); } [Fact] public void Disposing_twice_is_safe() { // The container disposes each page once per registration — its own type and PageViewModel. var page = Build(new ProxyPool([], new FakeProxyProbe(), new ProxyOptions())); page.Dispose(); Should.NotThrow(page.Dispose); } [Fact] public async Task The_summary_follows_the_pool_without_being_asked() { // The page has to react to a sweep it did not start; nobody presses refresh on a dashboard. var endpoint = Endpoint("1.2.3.4"); var pool = new ProxyPool( [new FakeProxySource([endpoint])], new FakeProxyProbe().Set(endpoint, alive: true), new ProxyOptions() ); using var page = Build(pool); page.ProxyTotal.ShouldBe(0); await pool.RefreshAsync(TestContext.Current.CancellationToken); page.ProxyTotal.ShouldBe(1); } }