using AvParser.Core.Collecting; using AvParser.Core.Collecting.Sources; namespace AvParser.Core.Tests.Collecting; public class MediaSourceCatalogTests { private sealed class FakeStore(IEnumerable? seed = null) : IUserSourceStore { private readonly List _configs = seed?.ToList() ?? []; public event EventHandler? Changed; public IReadOnlyList List() => [.. _configs]; public Task AddAsync( PatternSourceConfig config, CancellationToken cancellationToken = default ) { _configs.Add(config); Changed?.Invoke(this, EventArgs.Empty); return Task.FromResult(config); } public Task UpdateAsync(PatternSourceConfig config, CancellationToken cancellationToken = default) { var index = _configs.FindIndex(c => c.Id == config.Id); if (index < 0) { return Task.FromResult(false); } _configs[index] = config; Changed?.Invoke(this, EventArgs.Empty); return Task.FromResult(true); } public Task RemoveAsync(string id, CancellationToken cancellationToken = default) { var removed = _configs.RemoveAll(c => c.Id == id) > 0; if (removed) { Changed?.Invoke(this, EventArgs.Empty); } return Task.FromResult(removed); } } private static PatternSourceConfig Config(string id, string name) { PatternSourceConfig.TryCreate( name, "https://h/x/", 6, 8, IdAlphabet.Digits, null, null, allowDirectConnection: false, out var config, id ); return config!; } [Fact] public void An_empty_store_is_a_valid_empty_catalog() { using var catalog = new MediaSourceCatalog(new FakeStore()); catalog.Sources.ShouldBeEmpty(); catalog.Find("anything").ShouldBeNull(); } [Fact] public void Sources_are_materialised_from_the_stored_configs() { using var catalog = new MediaSourceCatalog(new FakeStore([Config("s1", "Beta"), Config("s2", "Alpha")])); // Ordered by display name. catalog.Sources.Select(s => s.Id).ShouldBe(["s2", "s1"]); catalog.Find("s1").ShouldNotBeNull().DisplayName.ShouldBe("Beta"); } [Fact] public async Task Adding_through_the_catalog_rebuilds_and_signals() { var store = new FakeStore(); using var catalog = new MediaSourceCatalog(store); var changed = 0; catalog.Changed += (_, _) => changed++; await catalog.AddAsync(Config("s1", "One"), TestContext.Current.CancellationToken); changed.ShouldBe(1); catalog.Sources.ShouldHaveSingleItem().Id.ShouldBe("s1"); } [Fact] public async Task Removing_through_the_catalog_drops_the_source() { using var catalog = new MediaSourceCatalog(new FakeStore([Config("s1", "One")])); await catalog.RemoveAsync("s1", TestContext.Current.CancellationToken); catalog.Sources.ShouldBeEmpty(); } }