diff --git a/src/Recyclarr.TestLibrary/IntegrationFixture.cs b/src/Recyclarr.TestLibrary/IntegrationFixture.cs index 005e3bf4..f10769c8 100644 --- a/src/Recyclarr.TestLibrary/IntegrationFixture.cs +++ b/src/Recyclarr.TestLibrary/IntegrationFixture.cs @@ -22,13 +22,15 @@ public abstract class IntegrationFixture : IDisposable { protected IntegrationFixture() { - var compRoot = new CompositionRoot(); - ServiceLocator = compRoot.Setup(builder => + Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr")); + Logger = CreateLogger(); + + Container = CompositionRoot.Setup(builder => { builder.RegisterInstance(Fs).As(); - builder.RegisterInstance(new AppPaths(Fs.CurrentDirectory())).As(); + builder.RegisterInstance(Paths).As(); builder.RegisterInstance(Console).As(); - builder.Register(_ => CreateLogger()).As().SingleInstance(); + builder.RegisterInstance(Logger).As().SingleInstance(); RegisterMockFor(builder); RegisterMockFor(builder); @@ -45,20 +47,21 @@ public abstract class IntegrationFixture : IDisposable { return new LoggerConfiguration() .MinimumLevel.Is(LogEventLevel.Debug) - .WriteTo.Console() + .WriteTo.TestCorrelator() .CreateLogger(); } private void SetupMetadataJson() { - var paths = Resolve(); - var metadataFile = paths.RepoDirectory.File("metadata.json"); + var metadataFile = Paths.RepoDirectory.File("metadata.json"); Fs.AddFileFromResource(metadataFile, "metadata.json"); } protected MockFileSystem Fs { get; } = new(); protected FakeInMemoryConsole Console { get; } = new(); - protected IServiceLocatorProxy ServiceLocator { get; } + protected ILifetimeScope Container { get; } + protected IAppPaths Paths { get; } + protected ILogger Logger { get; } private static void RegisterMockFor(ContainerBuilder builder) where T : class { @@ -67,13 +70,13 @@ public abstract class IntegrationFixture : IDisposable protected T Resolve(Action customRegistrations) where T : notnull { - var childScope = ServiceLocator.Container.BeginLifetimeScope(customRegistrations); + var childScope = Container.BeginLifetimeScope(customRegistrations); return childScope.Resolve(); } protected T Resolve() where T : notnull { - return ServiceLocator.Resolve(); + return Container.Resolve(); } protected virtual void Dispose(bool disposing) @@ -83,7 +86,7 @@ public abstract class IntegrationFixture : IDisposable return; } - ServiceLocator.Dispose(); + Container.Dispose(); Console.Dispose(); } diff --git a/src/Recyclarr.Tests/BaseCommandSetupIntegrationTest.cs b/src/Recyclarr.Tests/BaseCommandSetupIntegrationTest.cs index 721459a5..33f687ef 100644 --- a/src/Recyclarr.Tests/BaseCommandSetupIntegrationTest.cs +++ b/src/Recyclarr.Tests/BaseCommandSetupIntegrationTest.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using Recyclarr.Command.Setup; using Recyclarr.TestLibrary; using TrashLib.Config.Settings; -using TrashLib.Startup; namespace Recyclarr.Tests; @@ -27,54 +26,50 @@ public class BaseCommandSetupIntegrationTest : IntegrationFixture [Test] public void Log_janitor_cleans_up_user_specified_max_files() { - var paths = Resolve(); const int maxFiles = 25; - Fs.AddFile(paths.SettingsPath.FullName, new MockFileData($@" + Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData($@" log_janitor: max_files: {maxFiles} ")); for (var i = 0; i < maxFiles + 20; ++i) { - Fs.AddFile(paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); + Fs.AddFile(Paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); } var sut = Resolve(); sut.OnFinish(); - Fs.AllFiles.Where(x => x.StartsWith(paths.LogDirectory.FullName)) + Fs.AllFiles.Where(x => x.StartsWith(Paths.LogDirectory.FullName)) .Should().HaveCount(maxFiles); } [Test] public void Log_janitor_cleans_up_default_max_files() { - var paths = Resolve(); var settingsProvider = Resolve(); var maxFiles = settingsProvider.Settings.LogJanitor.MaxFiles; for (var i = 0; i < maxFiles + 20; ++i) { - Fs.AddFile(paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); + Fs.AddFile(Paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); } var sut = Resolve(); sut.OnFinish(); maxFiles.Should().BeGreaterThan(0); - Fs.AllFiles.Where(x => x.StartsWith(paths.LogDirectory.FullName)) + Fs.AllFiles.Where(x => x.StartsWith(Paths.LogDirectory.FullName)) .Should().HaveCount(maxFiles); } [Test] public void App_paths_setup_creates_initial_directories() { - var paths = Resolve(); - for (var i = 0; i < 50; ++i) { - Fs.AddFile(paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); + Fs.AddFile(Paths.LogDirectory.File($"logfile-{i}.log").FullName, new MockFileData("")); } var sut = Resolve(); @@ -82,9 +77,9 @@ log_janitor: var expectedDirs = new[] { - paths.LogDirectory.FullName, - paths.RepoDirectory.FullName, - paths.CacheDirectory.FullName + Paths.LogDirectory.FullName, + Paths.RepoDirectory.FullName, + Paths.CacheDirectory.FullName }; expectedDirs.Should().IntersectWith(Fs.AllDirectories); diff --git a/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs b/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs index 35292ee7..5ef8e1aa 100644 --- a/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs +++ b/src/Recyclarr.Tests/Command/CreateConfigCommandTest.cs @@ -1,58 +1,43 @@ using System.IO.Abstractions; using System.IO.Abstractions.Extensions; -using System.IO.Abstractions.TestingHelpers; -using AutoFixture.NUnit3; -using CliFx.Infrastructure; using FluentAssertions; -using NSubstitute; using NUnit.Framework; using Recyclarr.Command; -using TestLibrary.AutoFixture; -using TrashLib.TestLibrary; +using Recyclarr.TestLibrary; // ReSharper disable MethodHasAsyncOverload namespace Recyclarr.Tests.Command; [TestFixture] -// Cannot be parallelized due to static CompositionRoot property -public class CreateConfigCommandTest +[Parallelizable(ParallelScope.All)] +public class CreateConfigCommandTest : IntegrationFixture { - [Test, AutoMockData] - public async Task Config_file_created_when_using_default_path( - [Frozen(Matching.ImplementedInterfaces)] TestAppPaths paths, - [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - IServiceLocatorProxy container, - ICompositionRoot compositionRoot, - CreateConfigCommand sut) + [Test] + public async Task Config_file_created_when_using_default_path() { - BaseCommand.CompositionRoot = compositionRoot; + var sut = new CreateConfigCommand(); - await sut.Process(container); + await sut.Process(Container); - var file = fs.GetFile(paths.ConfigPath.FullName); + var file = Fs.GetFile(Paths.ConfigPath.FullName); file.Should().NotBeNull(); file.Contents.Should().NotBeEmpty(); } - [Test, AutoMockData] - public async Task Config_file_created_when_using_user_specified_path( - [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - [Frozen(Matching.ImplementedInterfaces)] TestAppPaths paths, - ICompositionRoot compositionRoot, - CreateConfigCommand sut) + [Test] + public async Task Config_file_created_when_using_user_specified_path() { - BaseCommand.CompositionRoot = compositionRoot; - - var ymlPath = fs.CurrentDirectory() + var sut = new CreateConfigCommand(); + var ymlPath = Fs.CurrentDirectory() .SubDirectory("user") .SubDirectory("specified") .File("file.yml").FullName; sut.AppDataDirectory = ymlPath; - await sut.ExecuteAsync(Substitute.For()); + await sut.Process(Container); - var file = fs.GetFile(ymlPath); + var file = Fs.GetFile(ymlPath); file.Should().NotBeNull(); file.Contents.Should().NotBeEmpty(); } diff --git a/src/Recyclarr.Tests/Command/Data/optionals.json b/src/Recyclarr.Tests/Command/Data/optionals.json new file mode 100644 index 00000000..d9240d2b --- /dev/null +++ b/src/Recyclarr.Tests/Command/Data/optionals.json @@ -0,0 +1,67 @@ +{ + "name": "Optionals", + "trash_id": "76e060895c5b8a765c310933da0a5357", + "ignored": [{ + "name": "Golden rule", + "trash_id": "cec8880b847dd5d31d29167ee0112b57", + "term": "/^(?=.*(1080|720))(?=.*((x|h)[ ._-]?265|hevc)).*/i" + }, { + "name": "Ignore Dolby Vision without HDR10 fallback.", + "trash_id": "436f5a7d08fbf02ba25cb5e5dfe98e55", + "term": "/^(?!.*(HDR|HULU|REMUX))(?=.*\\b(DV|Dovi|Dolby[- .]?Vision)\\b).*/i" + }, { + "name": "Ignore The Group -SCENE", + "trash_id": "f3f0f3691c6a1988d4a02963e69d11f2", + "term": "/\\b(-scene)\\b/i" + }, { + "name": "Ignore so called scene releases", + "trash_id": "5bc23c3a055a1a5d8bbe4fb49d80e0cb", + "term": "/^(?!.*(web[ ]dl|-deflate|-inflate))(?=.*([_. ]WEB[_. ]|-CAKES\\b|-GGEZ\\b|-GGWP\\b|-GLHF\\b|-GOSSIP\\b|-KOGI\\b|-PECULATE\\b)).*/i" + }, { + "name": "Dislike Bad Dual Audio Groups", + "trash_id": "538bad00ee6f8aced8e0db5218b8484c", + "term": "/\\b(-alfaHD|-BAT|-BNd|-C\\.A\\.A|-Cory|-FF|-FOXX|-G4RiS|-GUEIRA|-N3G4N|-PD|-RiPER|-RK|-SiGLA|-Tars|-WTV|-Yatogam1|-YusukeFLA)\\b/i" + }], + "required": [], + "preferred": [{ + "score": 15, + "terms": [{ + "name": "Prefer Season Packs", + "trash_id": "ea83f4740cec4df8112f3d6dd7c82751", + "term": "/\\bS\\d+\\b(?!E\\d+\\b)/i" + }] + }, { + "score": 10, + "terms": [{ + "name": "Prefer HDR", + "trash_id": "bc7a6383cbe88c3ee2d6396e1aacc0b3", + "term": "/\\bHDR(\\b|\\d)/i" + }] + }, { + "score": 100, + "terms": [{ + "name": "Prefer Dolby Vision", + "trash_id": "fa47da3377076d82d07c4e95b3f13d07", + "term": "/\\b(dv|dovi|dolby[ .]?vision)\\b/i" + }] + }, { + "score": -25, + "terms": [{ + "name": "Dislike retags: rartv, rarbg, eztv, TGx", + "trash_id": "6f2aefa61342a63387f2a90489e90790", + "term": "/(\\[rartv\\]|\\[rarbg\\]|\\[eztv\\]|\\[TGx\\])/i" + }, { + "name": "Dislike retagged groups", + "trash_id": "19cd5ecc0a24bf493a75e80a51974cdd", + "term": "/(-4P|-4Planet|-AsRequested|-BUYMORE|-CAPTCHA|-Chamele0n|-GEROV|-iNC0GNiTO|-NZBGeek|-Obfuscated|-postbot|-Rakuv|-Scrambled|-WhiteRev|-WRTEAM|-xpost)\\b/i" + }, { + "name": "Dislike release ending: en", + "trash_id": "6a7b462c6caee4a991a9d8aa38ce2405", + "term": "/\\s?\\ben\\b$/i" + }, { + "name": "Dislike release containing: 1-", + "trash_id": "236a3626a07cacf5692c73cc947bc280", + "term": "/(? await sut.ExecuteAsync(console); + var act = async () => await sut.Process(Container); await act.Should().ThrowAsync(); } @@ -39,42 +40,40 @@ public class SonarrCommandTest // If the user specifies a blank string as the value, it should still fail. sut.ListTerms = ""; - var act = async () => await sut.ExecuteAsync(console); + var act = async () => await sut.Process(Container); await act.Should().ThrowAsync(); } - [Test, AutoMockData] - public async Task List_terms_uses_specified_trash_id( - [Frozen] ISonarrGuideDataLister lister, - IConsole console, - ICompositionRoot compositionRoot, - SonarrCommand sut) + [Test] + public async Task List_terms_uses_specified_trash_id() { - BaseCommand.CompositionRoot = compositionRoot; - sut.ListReleaseProfiles = false; + var repoPaths = Resolve().Create(); + var cfDir = repoPaths.SonarrReleaseProfilePaths.First(); + Fs.AddFileFromResource(cfDir.File("optionals.json"), "optionals.json"); - sut.ListTerms = "some_id"; + var sut = new SonarrCommand + { + ListReleaseProfiles = false, + ListTerms = "76e060895c5b8a765c310933da0a5357" + }; - await sut.ExecuteAsync(console); + await sut.Process(Container); - lister.Received().ListTerms("some_id"); + Console.ReadOutputString().Should().Contain("List of Terms"); } - [Test, AutoMockData] - public async Task List_release_profiles_is_invoked( - [Frozen] ISonarrGuideDataLister lister, - IConsole console, - ICompositionRoot compositionRoot, - SonarrCommand sut) + [Test] + public async Task List_release_profiles_is_invoked() { - BaseCommand.CompositionRoot = compositionRoot; - - sut.ListReleaseProfiles = true; - sut.ListTerms = null; + var sut = new SonarrCommand + { + ListReleaseProfiles = true, + ListTerms = null + }; - await sut.ExecuteAsync(console); + await sut.Process(Container); - lister.Received().ListReleaseProfiles(); + Console.ReadOutputString().Should().Contain("List of Release Profiles"); } } diff --git a/src/Recyclarr.Tests/CompositionRootTest.cs b/src/Recyclarr.Tests/CompositionRootTest.cs index 89d96ad9..2bdad295 100644 --- a/src/Recyclarr.Tests/CompositionRootTest.cs +++ b/src/Recyclarr.Tests/CompositionRootTest.cs @@ -43,7 +43,7 @@ public class CompositionRootTest { var act = () => { - using var container = new CompositionRoot().Setup().Container; + using var container = CompositionRoot.Setup(); service.Instantiate(container); }; @@ -61,7 +61,7 @@ public class CompositionRootTest public ConcreteTypeEnumerator() { - _container = new CompositionRoot().Setup().Container; + _container = CompositionRoot.Setup(); } public IEnumerator GetEnumerator() @@ -91,7 +91,7 @@ public class CompositionRootTest [TestCaseSource(typeof(ConcreteTypeEnumerator))] public void Service_should_be_instantiable(Type service) { - using var container = new CompositionRoot().Setup(RegisterAdditionalServices).Container; + using var container = CompositionRoot.Setup(RegisterAdditionalServices); container.Resolve(service).Should().NotBeNull(); } } diff --git a/src/Recyclarr.Tests/Migration/MigrationExecutorTest.cs b/src/Recyclarr.Tests/Migration/MigrationExecutorTest.cs index 28b5c510..b40529db 100644 --- a/src/Recyclarr.Tests/Migration/MigrationExecutorTest.cs +++ b/src/Recyclarr.Tests/Migration/MigrationExecutorTest.cs @@ -16,7 +16,7 @@ public class MigrationExecutorTest [Test] public void Migration_steps_are_in_expected_order() { - var container = new CompositionRoot().Setup(builder => + var container = CompositionRoot.Setup(builder => { builder.RegisterInstance(Substitute.For()); }); diff --git a/src/Recyclarr.Tests/ServiceCompatibilityIntegrationTest.cs b/src/Recyclarr.Tests/ServiceCompatibilityIntegrationTest.cs index 90f66205..a49571a9 100644 --- a/src/Recyclarr.Tests/ServiceCompatibilityIntegrationTest.cs +++ b/src/Recyclarr.Tests/ServiceCompatibilityIntegrationTest.cs @@ -3,7 +3,6 @@ using FluentAssertions; using NUnit.Framework; using Recyclarr.TestLibrary; using TrashLib.Config.Settings; -using TrashLib.Startup; namespace Recyclarr.Tests; @@ -15,7 +14,6 @@ public class ServiceCompatibilityIntegrationTest : IntegrationFixture public void Load_settings_yml_correctly_when_file_exists() { var sut = Resolve(); - var paths = Resolve(); // For this test, it doesn't really matter if the YAML data matches what SettingsValue expects. // This test only ensures that the data deserialized is from the actual correct file. @@ -24,7 +22,7 @@ repository: clone_url: http://the_url.com "; - Fs.AddFile(paths.SettingsPath.FullName, new MockFileData(yamlData)); + Fs.AddFile(Paths.SettingsPath.FullName, new MockFileData(yamlData)); var settings = sut.Settings; diff --git a/src/Recyclarr/Command/BaseCommand.cs b/src/Recyclarr/Command/BaseCommand.cs index fd7b31cd..32d1dbc7 100644 --- a/src/Recyclarr/Command/BaseCommand.cs +++ b/src/Recyclarr/Command/BaseCommand.cs @@ -1,7 +1,6 @@ using Autofac; using CliFx; using CliFx.Attributes; -using CliFx.Exceptions; using CliFx.Infrastructure; using JetBrains.Annotations; using MoreLinq.Extensions; @@ -23,8 +22,6 @@ public abstract class BaseCommand : ICommand "Display additional logs useful for development/debug purposes.")] public bool Debug { get; [UsedImplicitly] set; } = false; - public static ICompositionRoot? CompositionRoot { get; set; } - protected virtual void RegisterServices(ContainerBuilder builder) { } @@ -34,12 +31,7 @@ public abstract class BaseCommand : ICommand // Must happen first because everything can use the logger. var logLevel = Debug ? LogEventLevel.Debug : LogEventLevel.Information; - if (CompositionRoot is null) - { - throw new CommandException("CompositionRoot must not be null"); - } - - using var container = CompositionRoot.Setup(builder => + await using var container = CompositionRoot.Setup(builder => { builder.RegisterInstance(console).As().ExternallyOwned(); @@ -67,5 +59,5 @@ public abstract class BaseCommand : ICommand } } - public abstract Task Process(IServiceLocatorProxy container); + public abstract Task Process(ILifetimeScope container); } diff --git a/src/Recyclarr/Command/CreateConfigCommand.cs b/src/Recyclarr/Command/CreateConfigCommand.cs index e6901d77..39a7f483 100644 --- a/src/Recyclarr/Command/CreateConfigCommand.cs +++ b/src/Recyclarr/Command/CreateConfigCommand.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using Autofac; using CliFx.Attributes; using CliFx.Exceptions; using Common; @@ -18,7 +19,7 @@ public class CreateConfigCommand : BaseCommand "directory")] public override string? AppDataDirectory { get; set; } - public override async Task Process(IServiceLocatorProxy container) + public override async Task Process(ILifetimeScope container) { var fs = container.Resolve(); var paths = container.Resolve(); diff --git a/src/Recyclarr/Command/MigrateCommand.cs b/src/Recyclarr/Command/MigrateCommand.cs index 5a96ef0b..e04a29f3 100644 --- a/src/Recyclarr/Command/MigrateCommand.cs +++ b/src/Recyclarr/Command/MigrateCommand.cs @@ -1,4 +1,5 @@ using System.Text; +using Autofac; using CliFx.Attributes; using CliFx.Exceptions; using JetBrains.Annotations; @@ -15,7 +16,7 @@ public class MigrateCommand : BaseCommand "Mainly for usage in Docker; not recommended for normal use.")] public override string? AppDataDirectory { get; set; } - public override Task Process(IServiceLocatorProxy container) + public override Task Process(ILifetimeScope container) { var migration = container.Resolve(); diff --git a/src/Recyclarr/Command/RadarrCommand.cs b/src/Recyclarr/Command/RadarrCommand.cs index 0eafba3c..1cfc7129 100644 --- a/src/Recyclarr/Command/RadarrCommand.cs +++ b/src/Recyclarr/Command/RadarrCommand.cs @@ -28,7 +28,7 @@ internal class RadarrCommand : ServiceCommand public override string Name => "Radarr"; - public override async Task Process(IServiceLocatorProxy container) + public override async Task Process(ILifetimeScope container) { await base.Process(container); @@ -51,7 +51,7 @@ internal class RadarrCommand : ServiceCommand foreach (var config in configLoader.LoadMany(Config, "radarr")) { - await using var scope = container.Container.BeginLifetimeScope(builder => + await using var scope = container.BeginLifetimeScope(builder => { builder.RegisterInstance(config).As(); }); diff --git a/src/Recyclarr/Command/ServiceCommand.cs b/src/Recyclarr/Command/ServiceCommand.cs index d82e5bca..1672ebca 100644 --- a/src/Recyclarr/Command/ServiceCommand.cs +++ b/src/Recyclarr/Command/ServiceCommand.cs @@ -69,7 +69,7 @@ public abstract class ServiceCommand : BaseCommand, IServiceCommand } } - public override Task Process(IServiceLocatorProxy container) + public override Task Process(ILifetimeScope container) { var log = container.Resolve(); var settingsProvider = container.Resolve(); diff --git a/src/Recyclarr/Command/SonarrCommand.cs b/src/Recyclarr/Command/SonarrCommand.cs index 64c70c2d..32a81da7 100644 --- a/src/Recyclarr/Command/SonarrCommand.cs +++ b/src/Recyclarr/Command/SonarrCommand.cs @@ -42,7 +42,7 @@ public class SonarrCommand : ServiceCommand public override string Name => "Sonarr"; - public override async Task Process(IServiceLocatorProxy container) + public override async Task Process(ILifetimeScope container) { await base.Process(container); @@ -86,7 +86,7 @@ public class SonarrCommand : ServiceCommand foreach (var config in configLoader.LoadMany(Config, "sonarr")) { - await using var scope = container.Container.BeginLifetimeScope(builder => + await using var scope = container.BeginLifetimeScope(builder => { builder.RegisterInstance(config).As(); }); diff --git a/src/Recyclarr/CompositionRoot.cs b/src/Recyclarr/CompositionRoot.cs index 4d24e4c8..aa04b105 100644 --- a/src/Recyclarr/CompositionRoot.cs +++ b/src/Recyclarr/CompositionRoot.cs @@ -24,14 +24,14 @@ using YamlDotNet.Serialization; namespace Recyclarr; -public class CompositionRoot : ICompositionRoot +public static class CompositionRoot { - public IServiceLocatorProxy Setup(Action? extraRegistrations = null) + public static ILifetimeScope Setup(Action? extraRegistrations = null) { return Setup(new ContainerBuilder(), extraRegistrations); } - private IServiceLocatorProxy Setup(ContainerBuilder builder, Action? extraRegistrations = null) + private static ILifetimeScope Setup(ContainerBuilder builder, Action? extraRegistrations = null) { RegisterAppPaths(builder); RegisterLogger(builder); @@ -59,7 +59,7 @@ public class CompositionRoot : ICompositionRoot extraRegistrations?.Invoke(builder); - return new ServiceLocatorProxy(builder.Build()); + return builder.Build(); } private static void RegisterLogger(ContainerBuilder builder) diff --git a/src/Recyclarr/ICompositionRoot.cs b/src/Recyclarr/ICompositionRoot.cs deleted file mode 100644 index 918073ed..00000000 --- a/src/Recyclarr/ICompositionRoot.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Autofac; - -namespace Recyclarr; - -public interface ICompositionRoot -{ - IServiceLocatorProxy Setup(Action? extraRegistrations = null); -} diff --git a/src/Recyclarr/IServiceLocatorProxy.cs b/src/Recyclarr/IServiceLocatorProxy.cs deleted file mode 100644 index c1abc26a..00000000 --- a/src/Recyclarr/IServiceLocatorProxy.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Autofac; - -namespace Recyclarr; - -/// -/// This class exists to make unit testing easier. Many methods for ILifetimeScope are extension -/// methods and make unit testing more difficult. -/// This class wraps Autofac to make it more -/// "mockable". -/// -public interface IServiceLocatorProxy : IDisposable -{ - ILifetimeScope Container { get; } - T Resolve() where T : notnull; -} diff --git a/src/Recyclarr/Program.cs b/src/Recyclarr/Program.cs index b1b43885..49739392 100644 --- a/src/Recyclarr/Program.cs +++ b/src/Recyclarr/Program.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.Text; using Autofac; using CliFx; -using Recyclarr.Command; namespace Recyclarr; @@ -12,8 +11,6 @@ internal static class Program public static async Task Main() { - BaseCommand.CompositionRoot = new CompositionRoot(); - var status = await new CliApplicationBuilder() .AddCommands(GetAllCommandTypes()) .SetExecutableName(ExecutableName) diff --git a/src/Recyclarr/ServiceLocatorProxy.cs b/src/Recyclarr/ServiceLocatorProxy.cs deleted file mode 100644 index cd877d81..00000000 --- a/src/Recyclarr/ServiceLocatorProxy.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Autofac; - -namespace Recyclarr; - -public sealed class ServiceLocatorProxy : IServiceLocatorProxy -{ - public ServiceLocatorProxy(ILifetimeScope container) - { - Container = container; - } - - public ILifetimeScope Container { get; } - - public T Resolve() where T : notnull - { - return Container.Resolve(); - } - - public void Dispose() - { - Container.Dispose(); - } -} diff --git a/src/TrashLib.Tests/Config/Services/ServiceConfigurationTest.cs b/src/TrashLib.Tests/Config/Services/ServiceConfigurationTest.cs index 181b9d43..1ce5d973 100644 --- a/src/TrashLib.Tests/Config/Services/ServiceConfigurationTest.cs +++ b/src/TrashLib.Tests/Config/Services/ServiceConfigurationTest.cs @@ -1,3 +1,4 @@ +using Autofac; using FluentAssertions; using FluentValidation; using NUnit.Framework; @@ -16,7 +17,7 @@ public class ServiceConfigurationTest : IntegrationFixture // default construct which should yield default values (invalid) for all required properties var config = new ServiceConfiguration(); - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); @@ -45,7 +46,7 @@ public class ServiceConfigurationTest : IntegrationFixture } }; - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); diff --git a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs index c9d5a02b..f55f043a 100644 --- a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs +++ b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using Autofac; using FluentAssertions; using FluentValidation; using NUnit.Framework; @@ -25,7 +26,7 @@ public class RadarrConfigurationTest : IntegrationFixture } }; - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); result.IsValid.Should().BeTrue(); @@ -37,7 +38,7 @@ public class RadarrConfigurationTest : IntegrationFixture { // default construct which should yield default values (invalid) for all required properties var config = new RadarrConfiguration(); - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); @@ -79,7 +80,7 @@ public class RadarrConfigurationTest : IntegrationFixture } }; - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); result.IsValid.Should().BeTrue(); diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideServiceTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideServiceTest.cs index 1a6e75a0..02c7adf9 100644 --- a/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideServiceTest.cs +++ b/src/TrashLib.Tests/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideServiceTest.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using System.IO.Abstractions.Extensions; using System.IO.Abstractions.TestingHelpers; using AutoFixture.NUnit3; using FluentAssertions; @@ -10,7 +11,6 @@ using TestLibrary.AutoFixture; using TrashLib.Repo; using TrashLib.Services.Sonarr.ReleaseProfile; using TrashLib.Services.Sonarr.ReleaseProfile.Guide; -using TrashLib.Startup; namespace TrashLib.Tests.Sonarr.ReleaseProfile.Guide; @@ -21,7 +21,6 @@ public class LocalRepoSonarrGuideServiceTest [Test, AutoMockData] public void Get_custom_format_json_works( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - [Frozen] IAppPaths appPaths, [Frozen] IRepoPaths repoPaths, LocalRepoSonarrGuideService sut) { @@ -40,11 +39,8 @@ public class LocalRepoSonarrGuideServiceTest var mockData1 = MakeMockObject("first"); var mockData2 = MakeMockObject("second"); - - var baseDir = appPaths.RepoDirectory - .SubDirectory("docs") - .SubDirectory("json") - .SubDirectory("sonarr"); + var baseDir = fs.CurrentDirectory().SubDirectory("files"); + baseDir.Create(); fs.AddFile(baseDir.File("first.json").FullName, MockFileData(mockData1)); fs.AddFile(baseDir.File("second.json").FullName, MockFileData(mockData2)); @@ -63,14 +59,11 @@ public class LocalRepoSonarrGuideServiceTest [Test, AutoMockData] public void Json_exceptions_do_not_interrupt_parsing_other_files( [Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs, - [Frozen] IAppPaths appPaths, [Frozen] IRepoPaths repoPaths, LocalRepoSonarrGuideService sut) { - var rootPath = appPaths.RepoDirectory - .SubDirectory("docs") - .SubDirectory("json") - .SubDirectory("sonarr"); + var rootPath = fs.CurrentDirectory().SubDirectory("files"); + rootPath.Create(); var badData = "# comment"; var goodData = new ReleaseProfileData diff --git a/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs b/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs index 3b99f174..f75eab23 100644 --- a/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs +++ b/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs @@ -1,3 +1,4 @@ +using Autofac; using FluentAssertions; using FluentValidation; using NUnit.Framework; @@ -22,7 +23,7 @@ public class SonarrConfigurationTest : IntegrationFixture ReleaseProfiles = new[] {new ReleaseProfileConfig()} }; - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); @@ -50,7 +51,7 @@ public class SonarrConfigurationTest : IntegrationFixture } }; - var validator = ServiceLocator.Resolve>(); + var validator = Container.Resolve>(); var result = validator.Validate(config); result.IsValid.Should().BeTrue(); diff --git a/src/TrashLib/Repo/IRepoPathsFactory.cs b/src/TrashLib/Repo/IRepoPathsFactory.cs index f43cdc29..f666f846 100644 --- a/src/TrashLib/Repo/IRepoPathsFactory.cs +++ b/src/TrashLib/Repo/IRepoPathsFactory.cs @@ -3,4 +3,5 @@ namespace TrashLib.Repo; public interface IRepoPathsFactory { IRepoPaths Create(); + RepoMetadata Metadata { get; } } diff --git a/src/TrashLib/Repo/RepoPathsFactory.cs b/src/TrashLib/Repo/RepoPathsFactory.cs index fa47fc33..d69831bf 100644 --- a/src/TrashLib/Repo/RepoPathsFactory.cs +++ b/src/TrashLib/Repo/RepoPathsFactory.cs @@ -8,6 +8,8 @@ public class RepoPathsFactory : IRepoPathsFactory private readonly IAppPaths _paths; private readonly Lazy _metadata; + public RepoMetadata Metadata => _metadata.Value; + public RepoPathsFactory(IRepoMetadataParser parser, IAppPaths paths) { _paths = paths; @@ -18,7 +20,6 @@ public class RepoPathsFactory : IRepoPathsFactory { return listOfDirectories .Select(x => _paths.RepoDirectory.SubDirectory(x)) - .Where(x => x.Exists) .ToList(); } diff --git a/src/TrashLib/Services/Common/QualityDefinition/QualityGuideParser.cs b/src/TrashLib/Services/Common/QualityDefinition/QualityGuideParser.cs index d9d4908e..503d6e08 100644 --- a/src/TrashLib/Services/Common/QualityDefinition/QualityGuideParser.cs +++ b/src/TrashLib/Services/Common/QualityDefinition/QualityGuideParser.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using Common; using Common.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -17,8 +18,7 @@ internal class QualityGuideParser where T : class public ICollection GetQualities(IEnumerable jsonDirectories) { - return jsonDirectories - .SelectMany(x => x.GetFiles("*.json")) + return JsonUtils.GetJsonFilesInDirectories(jsonDirectories, _log) .Select(ParseQuality) .NotNull() .ToList(); diff --git a/src/TrashLib/Services/CustomFormat/Guide/CustomFormatLoader.cs b/src/TrashLib/Services/CustomFormat/Guide/CustomFormatLoader.cs index 8ef5cf6d..51e33fd4 100644 --- a/src/TrashLib/Services/CustomFormat/Guide/CustomFormatLoader.cs +++ b/src/TrashLib/Services/CustomFormat/Guide/CustomFormatLoader.cs @@ -1,6 +1,7 @@ using System.IO.Abstractions; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; +using Common; using Common.Extensions; using Newtonsoft.Json; using Serilog; @@ -26,7 +27,7 @@ public class CustomFormatLoader : ICustomFormatLoader IFileInfo collectionOfCustomFormats) { var categories = _categoryParser.Parse(collectionOfCustomFormats).AsReadOnly(); - var jsonFiles = jsonPaths.SelectMany(x => x.GetFiles("*.json")); + var jsonFiles = JsonUtils.GetJsonFilesInDirectories(jsonPaths, _log); return jsonFiles.ToObservable() .Select(x => Observable.Defer(() => LoadJsonFromFile(x, categories))) .Merge(8) diff --git a/src/TrashLib/Services/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideService.cs b/src/TrashLib/Services/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideService.cs index 2411ecd9..7c1ab38f 100644 --- a/src/TrashLib/Services/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideService.cs +++ b/src/TrashLib/Services/Sonarr/ReleaseProfile/Guide/LocalRepoSonarrGuideService.cs @@ -1,4 +1,5 @@ using System.IO.Abstractions; +using Common; using Common.Extensions; using MoreLinq; using Newtonsoft.Json; @@ -47,8 +48,7 @@ public class LocalRepoSonarrGuideService : ISonarrGuideService { var converter = new TermDataConverter(); var paths = _pathsFactory.Create(); - var tasks = paths.SonarrReleaseProfilePaths - .SelectMany(x => x.GetFiles("*.json")) + var tasks = JsonUtils.GetJsonFilesInDirectories(paths.SonarrReleaseProfilePaths, _log) .Select(x => LoadAndParseFile(x, converter)); var data = Task.WhenAll(tasks).Result