Files
av-parser/tests/AvParser.UI.Tests/DashboardViewModelTests.cs
T
Leonid Pershin eb5061ee23 Refactor media source handling and update collection options
- Updated `IMediaSourceCatalog` to support user-added media sources, allowing dynamic editing and management of sources.
- Removed the `UrlListSource` class as its functionality is now integrated into the new catalog structure.
- Enhanced `CollectOptions` to default `RequireProxy` to true, ensuring stricter handling of proxy requirements.
- Improved error handling in `ParseError` to include a `Subject` field for better context on failures.
- Adjusted dependency injection to reflect changes in media source management, removing old source registrations.
- Introduced background proxy checks to ensure a more robust proxy pool management during collection processes.

These changes streamline the media collection process and improve the overall user experience by providing clearer error reporting and more flexible source management.
2026-08-15 14:20:06 +03:00

161 lines
5.2 KiB
C#

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<AvParser.Core.Collecting.Sources.PatternSourceConfig> List() => [];
public Task<AvParser.Core.Collecting.Sources.PatternSourceConfig> AddAsync(
AvParser.Core.Collecting.Sources.PatternSourceConfig config,
CancellationToken cancellationToken = default
) => Task.FromResult(config);
public Task<bool> UpdateAsync(
AvParser.Core.Collecting.Sources.PatternSourceConfig config,
CancellationToken cancellationToken = default
) => Task.FromResult(false);
public Task<bool> 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);
}
}