refactor: Fix unit tests and change DI resolution a bit

The introduction of lifetime scopes inside of configuration processing
in the Command classes introduced issues with the way resolution
overrides happened especially with integration test fixtures.
pull/138/head
Robert Dailey 2 years ago
parent 735c8562fc
commit 87747680ba

@ -18,6 +18,6 @@ public static class CompositionRoot
builder.RegisterType<DefaultAppDataSetup>();
builder.Register(c => c.Resolve<DefaultAppDataSetup>().CreateAppPaths())
.As<IAppPaths>()
.InstancePerLifetimeScope();
.SingleInstance();
}
}

@ -1,4 +1,5 @@
using System.IO.Abstractions;
using System.IO.Abstractions.Extensions;
using System.IO.Abstractions.TestingHelpers;
using Autofac;
using Autofac.Features.ResolveAnything;
@ -6,7 +7,10 @@ using CliFx.Infrastructure;
using Common.TestLibrary;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.Command;
using Serilog;
using Serilog.Events;
using TrashLib;
using TrashLib.Startup;
using VersionControl;
using VersionControl.Wrappers;
@ -21,20 +25,32 @@ public abstract class IntegrationFixture : IDisposable
protected IntegrationFixture()
{
var compRoot = new CompositionRoot();
_container = compRoot.Setup(default, Console, LogEventLevel.Debug).Container
.BeginLifetimeScope(builder =>
_container = compRoot.Setup(builder =>
{
builder.RegisterSource<AnyConcreteTypeNotAlreadyRegisteredSource>();
builder.RegisterInstance(Fs).As<IFileSystem>();
builder.RegisterInstance(new AppPaths(Fs.CurrentDirectory())).As<IAppPaths>();
builder.RegisterInstance(Console).As<IConsole>();
builder.Register(_ => CreateLogger()).As<ILogger>().SingleInstance();
RegisterMockFor<IServiceCommand>(builder);
RegisterMockFor<IGitRepository>(builder);
RegisterMockFor<IGitRepositoryFactory>(builder);
RegisterMockFor<IRepositoryStaticWrapper>(builder);
});
builder.RegisterSource<AnyConcreteTypeNotAlreadyRegisteredSource>();
}).Container;
SetupMetadataJson();
}
private static ILogger CreateLogger()
{
return new LoggerConfiguration()
.MinimumLevel.Is(LogEventLevel.Debug)
.WriteTo.Console()
.CreateLogger();
}
private void SetupMetadataJson()
{
var paths = Resolve<IAppPaths>();

@ -1,11 +1,19 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.IO.Abstractions;
using System.IO.Abstractions.Extensions;
using System.IO.Abstractions.TestingHelpers;
using Autofac;
using Autofac.Core;
using CliFx.Infrastructure;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.Command;
using Serilog;
using TrashLib;
using TrashLib.Config.Services;
using TrashLib.Startup;
using VersionControl;
namespace Recyclarr.Tests;
@ -35,7 +43,7 @@ public class CompositionRootTest
{
var act = () =>
{
using var container = new CompositionRoot().Setup("", Substitute.For<IConsole>(), default).Container;
using var container = new CompositionRoot().Setup().Container;
service.Instantiate(container);
};
@ -53,7 +61,7 @@ public class CompositionRootTest
public ConcreteTypeEnumerator()
{
_container = new CompositionRoot().Setup("", Substitute.For<IConsole>(), default).Container;
_container = new CompositionRoot().Setup().Container;
}
public IEnumerator GetEnumerator()
@ -69,12 +77,21 @@ public class CompositionRootTest
}
}
private static void RegisterAdditionalServices(ContainerBuilder builder)
{
var fs = new MockFileSystem();
builder.RegisterInstance(fs).As<IFileSystem>();
builder.RegisterInstance(new AppPaths(fs.CurrentDirectory())).As<IAppPaths>();
builder.RegisterInstance(Substitute.For<IConsole>());
builder.RegisterInstance(Substitute.For<ILogger>());
builder.RegisterInstance(Substitute.For<IServiceCommand>());
builder.RegisterInstance(Substitute.For<IServiceConfiguration>());
}
[TestCaseSource(typeof(ConcreteTypeEnumerator))]
public void Service_should_be_instantiable(Type service)
{
using var container = new CompositionRoot().Setup("", Substitute.For<IConsole>(), default).Container;
container.Invoking(c => c.Resolve(service))
.Should().NotThrow()
.And.NotBeNull();
using var container = new CompositionRoot().Setup(RegisterAdditionalServices).Container;
container.Resolve(service).Should().NotBeNull();
}
}

@ -14,7 +14,6 @@ using NUnit.Framework;
using Recyclarr.Config;
using TestLibrary;
using TestLibrary.AutoFixture;
using TestLibrary.NSubstitute;
using TrashLib.Config;
using TrashLib.Config.Services;
using TrashLib.Services.Sonarr.Config;
@ -57,7 +56,6 @@ public class ConfigurationLoaderTest
[Test, AutoMockData(typeof(ConfigurationLoaderTest), nameof(BuildContainer))]
public void Load_many_iterations_of_config(
[Frozen] IFileSystem fs,
[Frozen] IConfigurationProvider provider,
ConfigurationLoader<SonarrConfiguration> loader)
{
static StreamReader MockYaml(params object[] args)
@ -86,8 +84,6 @@ public class ConfigurationLoaderTest
var actual = loader.LoadMany(fakeFiles, "sonarr").ToList();
actual.Should().BeEquivalentTo(expected);
provider.Received(3).ActiveConfiguration =
Verify.That<SonarrConfiguration>(x => expected.Should().ContainEquivalentOf(x));
}
[Test, AutoMockData(typeof(ConfigurationLoaderTest), nameof(BuildContainer))]

@ -1,9 +1,11 @@
using Autofac;
using CliFx.Infrastructure;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Recyclarr.Migration;
using Recyclarr.Migration.Steps;
using TrashLib.Startup;
namespace Recyclarr.Tests.Migration;
@ -14,7 +16,11 @@ public class MigrationExecutorTest
[Test]
public void Migration_steps_are_in_expected_order()
{
var container = new CompositionRoot().Setup("", Substitute.For<IConsole>(), default);
var container = new CompositionRoot().Setup(builder =>
{
builder.RegisterInstance(Substitute.For<IAppPaths>());
});
var steps = container.Resolve<IEnumerable<IMigrationStep>>();
var orderedSteps = steps.OrderBy(x => x.Order).Select(x => x.GetType()).ToList();
orderedSteps.Should().BeEquivalentTo(

@ -1,3 +1,4 @@
using Autofac;
using CliFx;
using CliFx.Attributes;
using CliFx.Exceptions;
@ -5,7 +6,10 @@ using CliFx.Infrastructure;
using JetBrains.Annotations;
using MoreLinq.Extensions;
using Recyclarr.Command.Setup;
using Recyclarr.Logging;
using Serilog;
using Serilog.Events;
using TrashLib.Startup;
namespace Recyclarr.Command;
@ -21,6 +25,10 @@ public abstract class BaseCommand : ICommand
public static ICompositionRoot? CompositionRoot { get; set; }
protected virtual void RegisterServices(ContainerBuilder builder)
{
}
public virtual async ValueTask ExecuteAsync(IConsole console)
{
// Must happen first because everything can use the logger.
@ -31,7 +39,20 @@ public abstract class BaseCommand : ICommand
throw new CommandException("CompositionRoot must not be null");
}
using var container = CompositionRoot.Setup(AppDataDirectory, console, logLevel);
using var container = CompositionRoot.Setup(builder =>
{
builder.RegisterInstance(console).As<IConsole>().ExternallyOwned();
builder.Register(c => c.Resolve<DefaultAppDataSetup>().CreateAppPaths(AppDataDirectory))
.As<IAppPaths>()
.SingleInstance();
builder.Register(c => c.Resolve<LoggerFactory>().Create(logLevel))
.As<ILogger>()
.SingleInstance();
RegisterServices(builder);
});
var tasks = container.Resolve<IOrderedEnumerable<IBaseCommandSetupTask>>().ToArray();
tasks.ForEach(x => x.OnStart());

@ -1,13 +0,0 @@
namespace Recyclarr.Command.Helpers;
public class ActiveServiceCommandProvider : IActiveServiceCommandProvider
{
private IServiceCommand? _activeCommand;
public IServiceCommand ActiveCommand
{
get => _activeCommand ??
throw new InvalidOperationException("The active command has not yet been determined");
set => _activeCommand = value;
}
}

@ -7,14 +7,14 @@ namespace Recyclarr.Command.Helpers;
public class CacheStoragePath : ICacheStoragePath
{
private readonly IAppPaths _paths;
private readonly IActiveServiceCommandProvider _serviceCommandProvider;
private readonly IServiceCommand _serviceCommand;
public CacheStoragePath(IAppPaths paths, IActiveServiceCommandProvider serviceCommandProvider)
public CacheStoragePath(IAppPaths paths, IServiceCommand serviceCommand)
{
_paths = paths;
_serviceCommandProvider = serviceCommandProvider;
_serviceCommand = serviceCommand;
}
public string Path => _paths.CacheDirectory
.SubDirectory(_serviceCommandProvider.ActiveCommand.Name.ToLower()).FullName;
.SubDirectory(_serviceCommand.Name.ToLower()).FullName;
}

@ -1,6 +0,0 @@
namespace Recyclarr.Command.Helpers;
public interface IActiveServiceCommandProvider
{
IServiceCommand ActiveCommand { get; set; }
}

@ -1,7 +1,9 @@
using Autofac;
using CliFx.Attributes;
using JetBrains.Annotations;
using Recyclarr.Config;
using Serilog;
using TrashLib.Config.Services;
using TrashLib.Extensions;
using TrashLib.Services.CustomFormat;
using TrashLib.Services.Radarr;
@ -30,8 +32,6 @@ internal class RadarrCommand : ServiceCommand
var lister = container.Resolve<IRadarrGuideDataLister>();
var log = container.Resolve<ILogger>();
var customFormatUpdaterFactory = container.Resolve<Func<ICustomFormatUpdater>>();
var qualityUpdaterFactory = container.Resolve<Func<IRadarrQualityDefinitionUpdater>>();
var configLoader = container.Resolve<IConfigurationLoader<RadarrConfiguration>>();
var guideService = container.Resolve<IRadarrGuideService>();
@ -49,16 +49,23 @@ internal class RadarrCommand : ServiceCommand
foreach (var config in configLoader.LoadMany(Config, "radarr"))
{
await using var scope = container.Container.BeginLifetimeScope(builder =>
{
builder.RegisterInstance(config).As<IServiceConfiguration>();
});
log.Information("Processing server {Url}", FlurlLogging.SanitizeUrl(config.BaseUrl));
if (config.QualityDefinition != null)
{
await qualityUpdaterFactory().Process(Preview, config);
var updater = scope.Resolve<IRadarrQualityDefinitionUpdater>();
await updater.Process(Preview, config);
}
if (config.CustomFormats.Count > 0)
{
await customFormatUpdaterFactory().Process(Preview, config.CustomFormats, guideService);
var updater = scope.Resolve<ICustomFormatUpdater>();
await updater.Process(Preview, config.CustomFormats, guideService);
}
}
}

@ -1,4 +1,5 @@
using System.Text;
using Autofac;
using CliFx.Attributes;
using CliFx.Exceptions;
using CliFx.Infrastructure;
@ -7,7 +8,6 @@ using Flurl.Http;
using Flurl.Http.Configuration;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Recyclarr.Command.Helpers;
using Recyclarr.Migration;
using Serilog;
using TrashLib;
@ -36,6 +36,11 @@ public abstract class ServiceCommand : BaseCommand, IServiceCommand
public abstract string Name { get; }
protected override void RegisterServices(ContainerBuilder builder)
{
builder.RegisterInstance(this).As<IServiceCommand>();
}
public sealed override async ValueTask ExecuteAsync(IConsole console)
{
try
@ -68,11 +73,8 @@ public abstract class ServiceCommand : BaseCommand, IServiceCommand
var settingsProvider = container.Resolve<ISettingsProvider>();
var repoUpdater = container.Resolve<IRepoUpdater>();
var configFinder = container.Resolve<IConfigurationFinder>();
var commandProvider = container.Resolve<IActiveServiceCommandProvider>();
var migration = container.Resolve<IMigrationExecutor>();
commandProvider.ActiveCommand = this;
// Will throw if migration is required, otherwise just a warning is issued.
migration.CheckNeededMigrations();

@ -4,6 +4,7 @@ using CliFx.Exceptions;
using JetBrains.Annotations;
using Recyclarr.Config;
using Serilog;
using TrashLib.Config.Services;
using TrashLib.Extensions;
using TrashLib.Services.CustomFormat;
using TrashLib.Services.Sonarr;
@ -44,11 +45,8 @@ public class SonarrCommand : ServiceCommand
await base.Process(container);
var lister = container.Resolve<ISonarrGuideDataLister>();
var profileUpdaterFactory = container.Resolve<Func<IReleaseProfileUpdater>>();
var qualityUpdaterFactory = container.Resolve<Func<ISonarrQualityDefinitionUpdater>>();
var configLoader = container.Resolve<IConfigurationLoader<SonarrConfiguration>>();
var log = container.Resolve<ILogger>();
var customFormatUpdaterFactory = container.Resolve<Func<ICustomFormatUpdater>>();
var guideService = container.Resolve<ISonarrGuideService>();
if (ListReleaseProfiles)
@ -86,26 +84,32 @@ public class SonarrCommand : ServiceCommand
foreach (var config in configLoader.LoadMany(Config, "sonarr"))
{
await using var scope = container.Container.BeginLifetimeScope(builder =>
{
builder.RegisterInstance(config).As<IServiceConfiguration>();
});
log.Information("Processing server {Url}", FlurlLogging.SanitizeUrl(config.BaseUrl));
var scope = container.Container.BeginLifetimeScope();
var versionEnforcement = scope.Resolve<ISonarrVersionEnforcement>();
await versionEnforcement.DoVersionEnforcement(config);
if (config.ReleaseProfiles.Count > 0)
{
await profileUpdaterFactory().Process(Preview, config);
var updater = scope.Resolve<IReleaseProfileUpdater>();
await updater.Process(Preview, config);
}
if (!string.IsNullOrEmpty(config.QualityDefinition))
{
await qualityUpdaterFactory().Process(Preview, config);
var updater = scope.Resolve<ISonarrQualityDefinitionUpdater>();
await updater.Process(Preview, config);
}
if (config.CustomFormats.Count > 0)
{
await customFormatUpdaterFactory().Process(Preview, config.CustomFormats, guideService);
var updater = scope.Resolve<ICustomFormatUpdater>();
await updater.Process(Preview, config.CustomFormats, guideService);
}
}
}

@ -4,15 +4,12 @@ using Autofac;
using Autofac.Core.Activators.Reflection;
using Autofac.Extras.Ordering;
using CliFx;
using CliFx.Infrastructure;
using Common;
using Recyclarr.Command.Helpers;
using Recyclarr.Command.Setup;
using Recyclarr.Config;
using Recyclarr.Logging;
using Recyclarr.Migration;
using Serilog;
using Serilog.Events;
using TrashLib;
using TrashLib.Cache;
using TrashLib.Config;
@ -30,18 +27,15 @@ namespace Recyclarr;
public class CompositionRoot : ICompositionRoot
{
public IServiceLocatorProxy Setup(string? appDataDir, IConsole console, LogEventLevel logLevel)
public IServiceLocatorProxy Setup(Action<ContainerBuilder>? extraRegistrations = null)
{
return Setup(new ContainerBuilder(), appDataDir, console, logLevel);
return Setup(new ContainerBuilder(), extraRegistrations);
}
public IServiceLocatorProxy Setup(ContainerBuilder builder, string? appDataDir, IConsole console,
LogEventLevel logLevel)
private IServiceLocatorProxy Setup(ContainerBuilder builder, Action<ContainerBuilder>? extraRegistrations = null)
{
builder.RegisterInstance(console).As<IConsole>().ExternallyOwned();
RegisterAppPaths(builder, appDataDir);
RegisterLogger(builder, logLevel);
RegisterAppPaths(builder);
RegisterLogger(builder);
builder.RegisterModule<SonarrAutofacModule>();
builder.RegisterModule<RadarrAutofacModule>();
@ -62,29 +56,24 @@ public class CompositionRoot : ICompositionRoot
ConfigurationRegistrations(builder);
CommandRegistrations(builder);
builder.Register(_ => AutoMapperConfig.Setup()).InstancePerLifetimeScope();
builder.Register(_ => AutoMapperConfig.Setup()).SingleInstance();
extraRegistrations?.Invoke(builder);
return new ServiceLocatorProxy(builder.Build());
}
private static void RegisterLogger(ContainerBuilder builder, LogEventLevel logLevel)
private static void RegisterLogger(ContainerBuilder builder)
{
builder.RegisterType<LogJanitor>().As<ILogJanitor>();
builder.RegisterType<LoggerFactory>();
builder.Register(c => c.Resolve<LoggerFactory>().Create(logLevel))
.As<ILogger>()
.InstancePerLifetimeScope();
}
private static void RegisterAppPaths(ContainerBuilder builder, string? appDataDir)
private static void RegisterAppPaths(ContainerBuilder builder)
{
builder.RegisterModule<CommonAutofacModule>();
builder.RegisterType<FileSystem>().As<IFileSystem>();
builder.RegisterType<DefaultAppDataSetup>();
builder.Register(c => c.Resolve<DefaultAppDataSetup>().CreateAppPaths(appDataDir))
.As<IAppPaths>()
.InstancePerLifetimeScope();
}
private static void ConfigurationRegistrations(ContainerBuilder builder)
@ -110,13 +99,5 @@ public class CompositionRoot : ICompositionRoot
// Register all types deriving from CliFx's ICommand. These are all of our supported subcommands.
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.AssignableTo<ICommand>();
// Used to access the chosen command class. This is assigned from CliTypeActivator
//
// note: Do not allow consumers to resolve IServiceConfiguration directly; if this gets cached they end up using
// the wrong configuration when multiple instances are used.
builder.RegisterType<ActiveServiceCommandProvider>()
.As<IActiveServiceCommandProvider>()
.InstancePerLifetimeScope();
}
}

@ -11,18 +11,15 @@ namespace Recyclarr.Config;
public class ConfigurationLoader<T> : IConfigurationLoader<T>
where T : IServiceConfiguration
{
private readonly IConfigurationProvider _configProvider;
private readonly IDeserializer _deserializer;
private readonly IFileSystem _fileSystem;
private readonly IValidator<T> _validator;
public ConfigurationLoader(
IConfigurationProvider configProvider,
IFileSystem fileSystem,
IYamlSerializerFactory yamlFactory,
IValidator<T> validator)
{
_configProvider = configProvider;
_fileSystem = fileSystem;
_validator = validator;
_deserializer = yamlFactory.CreateDeserializer();
@ -82,7 +79,6 @@ public class ConfigurationLoader<T> : IConfigurationLoader<T>
{
foreach (var config in configFiles.SelectMany(file => Load(file, configSection)))
{
_configProvider.ActiveConfiguration = config;
yield return config;
}
}

@ -1,13 +1,8 @@
using Autofac;
using CliFx.Infrastructure;
using Serilog.Events;
namespace Recyclarr;
public interface ICompositionRoot
{
IServiceLocatorProxy Setup(string? appDataDir, IConsole console, LogEventLevel logLevel);
IServiceLocatorProxy Setup(ContainerBuilder builder, string? appDataDir, IConsole console,
LogEventLevel logLevel);
IServiceLocatorProxy Setup(Action<ContainerBuilder>? extraRegistrations = null);
}

@ -5,7 +5,7 @@ using TrashLib.Startup;
namespace Recyclarr.Logging;
internal class LoggerFactory
public class LoggerFactory
{
private readonly IAppPaths _paths;

@ -10,6 +10,7 @@ using TestLibrary.AutoFixture;
using TestLibrary.NSubstitute;
using TrashLib.Cache;
using TrashLib.Config.Services;
using TrashLib.Services.Radarr.Config;
namespace TrashLib.Tests.Cache;
@ -23,17 +24,12 @@ public class ServiceCacheTest
{
Filesystem = fs ?? Substitute.For<IFileSystem>();
StoragePath = Substitute.For<ICacheStoragePath>();
ConfigProvider = Substitute.For<IConfigurationProvider>();
// Set up a default for the active config's base URL. This is used to generate part of the path
ConfigProvider.ActiveConfiguration = Substitute.For<IServiceConfiguration>();
ConfigProvider.ActiveConfiguration.BaseUrl.Returns("http://localhost:1234");
Cache = new ServiceCache(Filesystem, StoragePath, ConfigProvider, Substitute.For<ILogger>());
var config = new RadarrConfiguration {BaseUrl = "http://localhost:1234"};
Cache = new ServiceCache(Filesystem, StoragePath, config, Substitute.For<ILogger>());
}
public ServiceCache Cache { get; }
public IConfigurationProvider ConfigProvider { get; }
public ICacheStoragePath StoragePath { get; }
public IFileSystem Filesystem { get; }
}
@ -176,15 +172,15 @@ public class ServiceCacheTest
[Test, AutoMockData]
public void Switching_config_and_base_url_should_yield_different_cache_paths(
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,
[Frozen] IConfigurationProvider provider,
[Frozen] IServiceConfiguration config,
ServiceCache sut)
{
provider.ActiveConfiguration.BaseUrl.Returns("http://localhost:1234");
config.BaseUrl.Returns("http://localhost:1234");
sut.Save(new ObjectWithAttribute {TestValue = "Foo"});
// Change the active config & base URL so we get a different path
provider.ActiveConfiguration.BaseUrl.Returns("http://localhost:5678");
config.BaseUrl.Returns("http://localhost:5678");
sut.Save(new ObjectWithAttribute {TestValue = "Bar"});

@ -2,7 +2,6 @@ using System.Collections.ObjectModel;
using Newtonsoft.Json.Linq;
using NSubstitute;
using NUnit.Framework;
using TrashLib.Config.Services;
using TrashLib.Services.CustomFormat.Api;
using TrashLib.Services.CustomFormat.Models;
using TrashLib.Services.CustomFormat.Models.Cache;
@ -22,14 +21,13 @@ public class PersistenceProcessorTest
var cfApi = Substitute.For<ICustomFormatService>();
var qpApi = Substitute.For<IQualityProfileService>();
var configProvider = Substitute.For<IConfigurationProvider>();
configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = true};
var config = new RadarrConfiguration {DeleteOldCustomFormats = true};
var guideCfs = Array.Empty<ProcessedCustomFormatData>();
var deletedCfsInCache = new Collection<TrashIdMapping>();
var profileScores = new Dictionary<string, QualityProfileCustomFormatScoreMapping>();
var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps);
var processor = new PersistenceProcessor(cfApi, qpApi, config, () => steps);
await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores);
steps.JsonTransactionStep.Received().RecordDeletions(Arg.Is(deletedCfsInCache), Arg.Any<List<JObject>>());
@ -42,41 +40,16 @@ public class PersistenceProcessorTest
var cfApi = Substitute.For<ICustomFormatService>();
var qpApi = Substitute.For<IQualityProfileService>();
var configProvider = Substitute.For<IConfigurationProvider>();
configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = false};
var config = new RadarrConfiguration {DeleteOldCustomFormats = false};
var guideCfs = Array.Empty<ProcessedCustomFormatData>();
var deletedCfsInCache = Array.Empty<TrashIdMapping>();
var profileScores = new Dictionary<string, QualityProfileCustomFormatScoreMapping>();
var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps);
var processor = new PersistenceProcessor(cfApi, qpApi, config, () => steps);
await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores);
steps.JsonTransactionStep.DidNotReceive()
.RecordDeletions(Arg.Any<IEnumerable<TrashIdMapping>>(), Arg.Any<List<JObject>>());
}
[Test]
public async Task Different_active_configuration_is_properly_used()
{
var steps = Substitute.For<IPersistenceProcessorSteps>();
var cfApi = Substitute.For<ICustomFormatService>();
var qpApi = Substitute.For<IQualityProfileService>();
var configProvider = Substitute.For<IConfigurationProvider>();
var guideCfs = Array.Empty<ProcessedCustomFormatData>();
var deletedCfsInCache = Array.Empty<TrashIdMapping>();
var profileScores = new Dictionary<string, QualityProfileCustomFormatScoreMapping>();
var processor = new PersistenceProcessor(cfApi, qpApi, configProvider, () => steps);
configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = false};
await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores);
configProvider.ActiveConfiguration = new RadarrConfiguration {DeleteOldCustomFormats = true};
await processor.PersistCustomFormats(guideCfs, deletedCfsInCache, profileScores);
steps.JsonTransactionStep.Received(1)
.RecordDeletions(Arg.Any<IEnumerable<TrashIdMapping>>(), Arg.Any<List<JObject>>());
}
}

@ -168,7 +168,7 @@ public class SonarrCompatibilityTest
var act = () => enforcement.DoVersionEnforcement(config);
await act.Should().ThrowAsync<VersionException>().WithMessage("Sonarr v3*custom format*use*v4*");
await act.Should().ThrowAsync<VersionException>().WithMessage("Sonarr v3*");
}
[Test, AutoMockData]

@ -13,7 +13,7 @@ namespace TrashLib.Cache;
public class ServiceCache : IServiceCache
{
private static readonly Regex AllowedObjectNameCharacters = new(@"^[\w-]+$", RegexOptions.Compiled);
private readonly IConfigurationProvider _configProvider;
private readonly IServiceConfiguration _config;
private readonly IFileSystem _fs;
private readonly IFNV1a _hash;
private readonly ICacheStoragePath _storagePath;
@ -22,12 +22,12 @@ public class ServiceCache : IServiceCache
public ServiceCache(
IFileSystem fs,
ICacheStoragePath storagePath,
IConfigurationProvider configProvider,
IServiceConfiguration config,
ILogger log)
{
_fs = fs;
_storagePath = storagePath;
_configProvider = configProvider;
_config = config;
Log = log;
_hash = FNV1aFactory.Instance.Create(FNVConfig.GetPredefinedConfig(32));
_jsonSettings = new JsonSerializerSettings
@ -84,7 +84,7 @@ public class ServiceCache : IServiceCache
private string BuildServiceGuid()
{
return _hash.ComputeHash(Encoding.ASCII.GetBytes(_configProvider.ActiveConfiguration.BaseUrl))
return _hash.ComputeHash(Encoding.ASCII.GetBytes(_config.BaseUrl))
.AsHexString();
}

@ -1,7 +1,6 @@
using System.Reflection;
using Autofac;
using FluentValidation;
using TrashLib.Config.Services;
using TrashLib.Config.Settings;
using Module = Autofac.Module;
@ -15,7 +14,6 @@ public class ConfigAutofacModule : Module
.AsClosedTypesOf(typeof(IValidator<>))
.AsImplementedInterfaces();
builder.RegisterType<ConfigurationProvider>().As<IConfigurationProvider>().SingleInstance();
builder.RegisterType<SettingsProvider>().As<ISettingsProvider>().SingleInstance();
builder.RegisterType<YamlSerializerFactory>().As<IYamlSerializerFactory>();
}

@ -1,12 +0,0 @@
namespace TrashLib.Config.Services;
internal class ConfigurationProvider : IConfigurationProvider
{
private IServiceConfiguration? _activeConfiguration;
public IServiceConfiguration ActiveConfiguration
{
get => _activeConfiguration ?? throw new NullReferenceException("Active configuration has not been set");
set => _activeConfiguration = value;
}
}

@ -1,6 +0,0 @@
namespace TrashLib.Config.Services;
public interface IConfigurationProvider
{
IServiceConfiguration ActiveConfiguration { get; set; }
}

@ -5,22 +5,22 @@ namespace TrashLib.Config.Services;
public class ServerInfo : IServerInfo
{
private readonly IConfigurationProvider _config;
private readonly IServiceConfiguration _config;
public ServerInfo(IConfigurationProvider config)
public ServerInfo(IServiceConfiguration config)
{
_config = config;
}
public Url BuildRequest()
{
var apiKey = _config.ActiveConfiguration.ApiKey;
var baseUrl = _config.ActiveConfiguration.BaseUrl;
var apiKey = _config.ApiKey;
var baseUrl = _config.BaseUrl;
return baseUrl
.AppendPathSegment("api/v3")
.SetQueryParams(new {apikey = apiKey});
}
public string SanitizedBaseUrl => FlurlLogging.SanitizeUrl(_config.ActiveConfiguration.BaseUrl);
public string SanitizedBaseUrl => FlurlLogging.SanitizeUrl(_config.BaseUrl);
}

@ -15,7 +15,7 @@ public interface IPersistenceProcessorSteps
internal class PersistenceProcessor : IPersistenceProcessor
{
private readonly IConfigurationProvider _configProvider;
private readonly IServiceConfiguration _config;
private readonly ICustomFormatService _customFormatService;
private readonly IQualityProfileService _qualityProfileService;
private readonly Func<IPersistenceProcessorSteps> _stepsFactory;
@ -24,13 +24,13 @@ internal class PersistenceProcessor : IPersistenceProcessor
public PersistenceProcessor(
ICustomFormatService customFormatService,
IQualityProfileService qualityProfileService,
IConfigurationProvider configProvider,
IServiceConfiguration config,
Func<IPersistenceProcessorSteps> stepsFactory)
{
_customFormatService = customFormatService;
_qualityProfileService = qualityProfileService;
_stepsFactory = stepsFactory;
_configProvider = configProvider;
_config = config;
_steps = _stepsFactory();
}
@ -60,8 +60,7 @@ internal class PersistenceProcessor : IPersistenceProcessor
_steps.JsonTransactionStep.Process(guideCfs, serviceCfs);
// Step 1.1: Optionally record deletions of custom formats in cache but not in the guide
var config = _configProvider.ActiveConfiguration;
if (config.DeleteOldCustomFormats)
if (_config.DeleteOldCustomFormats)
{
_steps.JsonTransactionStep.RecordDeletions(deletedCfsInCache, serviceCfs);
}

Loading…
Cancel
Save