using AvParser.Core.Collecting.Sources; using AvParser.Infrastructure.Collecting; using AvParser.Infrastructure.Storage; using Microsoft.Extensions.Logging.Abstractions; namespace AvParser.Infrastructure.Tests.Collecting; public sealed class JsonUserSourceStoreTests : IDisposable { private readonly string _directory = Path.Combine( Path.GetTempPath(), "AvParserTests", Guid.NewGuid().ToString("N") ); private JsonUserSourceStore Create() => new(new AppPaths(_directory), NullLogger.Instance); private static PatternSourceConfig Config(string id, string name = "Test", bool allowDirect = false) { PatternSourceConfig.TryCreate( name, "https://imgtest.example/test1/", 6, 8, IdAlphabet.Alphanumeric, null, ".jpg", allowDirect, out var config, id ); return config!; } public void Dispose() { if (Directory.Exists(_directory)) { Directory.Delete(_directory, recursive: true); } } [Fact] public void An_absent_file_reads_as_an_empty_list() { using var store = Create(); store.List().ShouldBeEmpty(); } [Fact] public async Task Added_sources_survive_a_reload() { using (var store = Create()) { await store.AddAsync(Config("s1", "Kept"), TestContext.Current.CancellationToken); } // A brand-new instance reads from disk rather than from the in-memory cache. using var reopened = Create(); var config = reopened.List().ShouldHaveSingleItem(); config.Id.ShouldBe("s1"); config.Name.ShouldBe("Kept"); config.BaseUrl.AbsoluteUri.ShouldBe("https://imgtest.example/test1/"); config.Extension.ShouldBe(".jpg"); } [Fact] public async Task Updating_replaces_the_matching_config() { using var store = Create(); await store.AddAsync(Config("s1", "Before"), TestContext.Current.CancellationToken); var updated = Config("s1", "After"); (await store.UpdateAsync(updated, TestContext.Current.CancellationToken)).ShouldBeTrue(); store.List().ShouldHaveSingleItem().Name.ShouldBe("After"); } [Fact] public async Task Updating_an_unknown_id_changes_nothing() { using var store = Create(); (await store.UpdateAsync(Config("ghost"), TestContext.Current.CancellationToken)).ShouldBeFalse(); store.List().ShouldBeEmpty(); } [Fact] public async Task Removing_takes_the_config_out() { using var store = Create(); await store.AddAsync(Config("s1"), TestContext.Current.CancellationToken); (await store.RemoveAsync("s1", TestContext.Current.CancellationToken)).ShouldBeTrue(); store.List().ShouldBeEmpty(); } [Fact] public async Task Mutations_raise_the_changed_event() { using var store = Create(); var changed = 0; store.Changed += (_, _) => changed++; await store.AddAsync(Config("s1"), TestContext.Current.CancellationToken); await store.RemoveAsync("s1", TestContext.Current.CancellationToken); changed.ShouldBe(2); } [Fact] public async Task A_corrupt_file_reads_as_an_empty_list() { Directory.CreateDirectory(_directory); IAppPaths paths = new AppPaths(_directory); await File.WriteAllTextAsync(paths.UserSourcesFile, "{ not json ]", TestContext.Current.CancellationToken); using var store = Create(); store.List().ShouldBeEmpty(); } }