- 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.
131 lines
4.6 KiB
C#
131 lines
4.6 KiB
C#
using AvParser.Core.Proxies;
|
|
using AvParser.Core.Settings;
|
|
using AvParser.Infrastructure.Proxies;
|
|
using AvParser.UI.Services;
|
|
using AvParser.UI.ViewModels;
|
|
using ReactiveUI.Primitives.Signals;
|
|
|
|
namespace AvParser.UI.HeadlessTests;
|
|
|
|
/// <summary>
|
|
/// Test doubles for the headless tests.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Intentionally duplicated from <c>AvParser.UI.Tests</c> rather than shared through a fourth
|
|
/// project: these are a few lines of trivial stand-ins, and a shared test-kit assembly would have
|
|
/// to opt out of the <c>tests/</c> conventions (self-executing xUnit exe) to build at all.
|
|
/// </remarks>
|
|
internal sealed class FakePage(string titleKey, string iconKey = "IconHome") : PageViewModel
|
|
{
|
|
public override string TitleKey { get; } = titleKey;
|
|
|
|
public override string IconKey { get; } = iconKey;
|
|
}
|
|
|
|
/// <inheritdoc cref="IThemeService" />
|
|
internal sealed class FakeThemeService(AppTheme initial = AppTheme.System) : IThemeService, IDisposable
|
|
{
|
|
private readonly BehaviorSignal<AppTheme> _current = new(initial);
|
|
|
|
public AppTheme Current => _current.Value;
|
|
|
|
public IObservable<AppTheme> Changes => _current;
|
|
|
|
public void Apply(AppTheme theme)
|
|
{
|
|
if (theme != _current.Value)
|
|
{
|
|
_current.OnNext(theme);
|
|
}
|
|
}
|
|
|
|
public void Dispose() => _current.Dispose();
|
|
}
|
|
|
|
/// <summary>In-memory settings, so tests never touch the developer's real profile.</summary>
|
|
internal sealed class FakeSettingsService(AppSettings? initial = null) : ISettingsService, IDisposable
|
|
{
|
|
private readonly BehaviorSignal<AppSettings> _current = new(initial ?? new AppSettings());
|
|
|
|
public AppSettings Current => _current.Value;
|
|
|
|
public IObservable<AppSettings> Changes => _current;
|
|
|
|
public void Update(Func<AppSettings, AppSettings> mutate) => _current.OnNext(mutate(_current.Value));
|
|
|
|
public Task FlushAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
|
|
|
public void Dispose() => _current.Dispose();
|
|
}
|
|
|
|
/// <summary>An editable proxy list held in memory.</summary>
|
|
internal sealed class FakeMutableProxySource : IMutableProxySource
|
|
{
|
|
public string Id => "fake-custom";
|
|
|
|
public string DisplayName => "Fake custom list";
|
|
|
|
public ProxySourceKind Kind => ProxySourceKind.Custom;
|
|
|
|
public List<ProxyEndpoint> Endpoints { get; } = [];
|
|
|
|
public Task<IReadOnlyList<ProxyEndpoint>> GetProxiesAsync(CancellationToken cancellationToken = default) =>
|
|
Task.FromResult<IReadOnlyList<ProxyEndpoint>>(Endpoints.ToArray());
|
|
|
|
public Task<int> AddAsync(IEnumerable<ProxyEndpoint> endpoints, CancellationToken cancellationToken = default)
|
|
{
|
|
Endpoints.AddRange(endpoints);
|
|
return Task.FromResult(Endpoints.Count);
|
|
}
|
|
|
|
public Task<bool> RemoveAsync(ProxyEndpoint endpoint, CancellationToken cancellationToken = default) =>
|
|
Task.FromResult(Endpoints.RemoveAll(e => e.Key == endpoint.Key) > 0);
|
|
|
|
public Task ClearAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
Endpoints.Clear();
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>A probe that says everything is dead. The view never calls it in these tests.</summary>
|
|
internal sealed class FakeProxyProbe : IProxyProbe
|
|
{
|
|
public Task<ProxyProbeResult> ProbeAsync(
|
|
ProxyEndpoint endpoint,
|
|
ProxyOptions options,
|
|
CancellationToken cancellationToken = default
|
|
) => Task.FromResult(ProxyProbeResult.Failure("not probed in tests"));
|
|
}
|
|
|
|
/// <summary>A remembered-state store that keeps everything in memory.</summary>
|
|
internal sealed class FakeProxyStateStore : IProxyStateStore
|
|
{
|
|
public Dictionary<string, ProxyStateRecord> State { get; } = new(StringComparer.Ordinal);
|
|
|
|
public Task<IReadOnlyDictionary<string, ProxyStateRecord>> LoadAsync(
|
|
CancellationToken cancellationToken = default
|
|
) => Task.FromResult<IReadOnlyDictionary<string, ProxyStateRecord>>(State);
|
|
|
|
public Task SaveAsync(IEnumerable<ProxyEntry> entries, CancellationToken cancellationToken = default) =>
|
|
Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary>
|
|
/// A loader that does nothing.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The real one probes on startup, which would race these view tests: the load is fired from the
|
|
/// page constructor and would mark every fake proxy dead partway through an assertion.
|
|
/// </remarks>
|
|
internal sealed class FakeProxyPoolLoader : IProxyPoolLoader
|
|
{
|
|
public bool IsLoaded => true;
|
|
|
|
public bool IsToppingUp => false;
|
|
|
|
public Task<ProxyPoolLoadResult> EnsureLoadedAsync() => Task.FromResult(new ProxyPoolLoadResult(0, 0, 0));
|
|
|
|
public Task SaveStateAsync(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
|
}
|