parent
67b2166d8b
commit
ee377e55fa
@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="config create -t" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/Recyclarr.Cli/bin/Debug/net7.0/recyclarr.exe" />
|
||||
<option name="PROGRAM_PARAMETERS" value="config create -t anime-radarr -f" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Recyclarr.Cli/bin/Debug/net7.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Recyclarr.Cli/Recyclarr.Cli.csproj" />
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net7.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
</configuration>
|
||||
</component>
|
@ -0,0 +1,8 @@
|
||||
namespace Recyclarr.Cli.Console.Settings;
|
||||
|
||||
public interface ICreateConfigSettings
|
||||
{
|
||||
public string? Path { get; }
|
||||
public IReadOnlyCollection<string> Templates { get; }
|
||||
public bool Force { get; }
|
||||
}
|
@ -1,46 +1,25 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.Common;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Startup;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public class ConfigCreationProcessor : IConfigCreationProcessor
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly IAppPaths _paths;
|
||||
private readonly IFileSystem _fs;
|
||||
private readonly IResourceDataReader _resources;
|
||||
private readonly IOrderedEnumerable<IConfigCreator> _creators;
|
||||
|
||||
public ConfigCreationProcessor(
|
||||
ILogger log,
|
||||
IAppPaths paths,
|
||||
IFileSystem fs,
|
||||
IResourceDataReader resources)
|
||||
public ConfigCreationProcessor(IOrderedEnumerable<IConfigCreator> creators)
|
||||
{
|
||||
_log = log;
|
||||
_paths = paths;
|
||||
_fs = fs;
|
||||
_resources = resources;
|
||||
_creators = creators;
|
||||
}
|
||||
|
||||
public async Task Process(string? configFilePath)
|
||||
public async Task Process(ICreateConfigSettings settings)
|
||||
{
|
||||
var configFile = configFilePath is null
|
||||
? _paths.AppDataDirectory.File("recyclarr.yml")
|
||||
: _fs.FileInfo.New(configFilePath);
|
||||
|
||||
if (configFile.Exists)
|
||||
var creator = _creators.FirstOrDefault(x => x.CanHandle(settings));
|
||||
if (creator is null)
|
||||
{
|
||||
throw new FileExistsException(configFile.FullName);
|
||||
throw new FatalException("Unable to determine which config creation logic to use");
|
||||
}
|
||||
|
||||
configFile.Directory?.Create();
|
||||
await using var stream = configFile.CreateText();
|
||||
|
||||
var ymlData = _resources.ReadData("config-template.yml");
|
||||
await stream.WriteAsync(ymlData);
|
||||
|
||||
_log.Information("Created configuration at: {Path}", configFile.FullName);
|
||||
await creator.Create(settings);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.TrashLib.Config.Parsing;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
/// <remarks>
|
||||
/// This was originally intended to be used by `config create`, but YamlDotNet cannot serialize
|
||||
/// comments so this
|
||||
/// class was not used. I kept it around in case I want to revisit later. There might be an
|
||||
/// opportunity to use this
|
||||
/// with the GUI.
|
||||
/// </remarks>
|
||||
public class ConfigManipulator : IConfigManipulator
|
||||
{
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly ConfigParser _configParser;
|
||||
private readonly ConfigSaver _configSaver;
|
||||
private readonly ConfigValidationExecutor _validator;
|
||||
|
||||
public ConfigManipulator(
|
||||
IAnsiConsole console,
|
||||
ConfigParser configParser,
|
||||
ConfigSaver configSaver,
|
||||
ConfigValidationExecutor validator)
|
||||
{
|
||||
_console = console;
|
||||
_configParser = configParser;
|
||||
_configSaver = configSaver;
|
||||
_validator = validator;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, TConfig> InvokeCallbackForEach<TConfig>(
|
||||
Func<string, ServiceConfigYaml, ServiceConfigYaml> editCallback,
|
||||
IReadOnlyDictionary<string, TConfig>? configs)
|
||||
where TConfig : ServiceConfigYaml
|
||||
{
|
||||
var newConfigs = new Dictionary<string, TConfig>();
|
||||
|
||||
if (configs is null)
|
||||
{
|
||||
return newConfigs;
|
||||
}
|
||||
|
||||
foreach (var (instanceName, config) in configs)
|
||||
{
|
||||
newConfigs[instanceName] = (TConfig) editCallback(instanceName, config);
|
||||
}
|
||||
|
||||
return newConfigs;
|
||||
}
|
||||
|
||||
public void LoadAndSave(
|
||||
IFileInfo source,
|
||||
IFileInfo destinationFile,
|
||||
Func<string, ServiceConfigYaml, ServiceConfigYaml> editCallback)
|
||||
{
|
||||
// Parse & save the template file to address the following:
|
||||
// - Find & report any syntax errors
|
||||
// - Run validation & report issues
|
||||
// - Consistently reformat the output file (when it is saved again)
|
||||
// - Ignore stuff for diffing purposes, such as comments.
|
||||
var config = _configParser.Load(source);
|
||||
if (config is null)
|
||||
{
|
||||
// Do not log here, since ConfigParser already has substantial logging
|
||||
throw new FileLoadException("Problem while loading config template");
|
||||
}
|
||||
|
||||
config = new RootConfigYaml
|
||||
{
|
||||
Radarr = InvokeCallbackForEach(editCallback, config.Radarr),
|
||||
Sonarr = InvokeCallbackForEach(editCallback, config.Sonarr)
|
||||
};
|
||||
|
||||
if (!_validator.Validate(config))
|
||||
{
|
||||
_console.WriteLine(
|
||||
"The configuration file will still be created, despite the previous validation errors. " +
|
||||
"You must open the file and correct the above issues before running a sync command.");
|
||||
}
|
||||
|
||||
_configSaver.Save(config, destinationFile);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public interface IConfigCreationProcessor
|
||||
{
|
||||
Task Process(string? configFilePath);
|
||||
Task Process(ICreateConfigSettings settings);
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public interface IConfigCreator
|
||||
{
|
||||
bool CanHandle(ICreateConfigSettings settings);
|
||||
Task Create(ICreateConfigSettings settings);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.TrashLib.Config.Parsing;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public interface IConfigManipulator
|
||||
{
|
||||
void LoadAndSave(
|
||||
IFileInfo source,
|
||||
IFileInfo destinationFile,
|
||||
Func<string, ServiceConfigYaml, ServiceConfigYaml> editCallback);
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Common;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Startup;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public class LocalConfigCreator : IConfigCreator
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly IAppPaths _paths;
|
||||
private readonly IFileSystem _fs;
|
||||
private readonly IResourceDataReader _resources;
|
||||
|
||||
public LocalConfigCreator(ILogger log, IAppPaths paths, IFileSystem fs, IResourceDataReader resources)
|
||||
{
|
||||
_log = log;
|
||||
_paths = paths;
|
||||
_fs = fs;
|
||||
_resources = resources;
|
||||
}
|
||||
|
||||
public bool CanHandle(ICreateConfigSettings settings)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task Create(ICreateConfigSettings settings)
|
||||
{
|
||||
var configFile = settings.Path is null
|
||||
? _paths.AppDataDirectory.File("recyclarr.yml")
|
||||
: _fs.FileInfo.New(settings.Path);
|
||||
|
||||
if (configFile.Exists)
|
||||
{
|
||||
throw new FileExistsException(configFile.FullName);
|
||||
}
|
||||
|
||||
configFile.CreateParentDirectory();
|
||||
await using var stream = configFile.CreateText();
|
||||
|
||||
var ymlData = _resources.ReadData("config-template.yml");
|
||||
await stream.WriteAsync(ymlData);
|
||||
|
||||
_log.Information("Created configuration at: {Path}", configFile.FullName);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Startup;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Config;
|
||||
|
||||
public class TemplateConfigCreator : IConfigCreator
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly IConfigTemplateGuideService _templates;
|
||||
|
||||
private readonly IAppPaths _paths;
|
||||
// private readonly IConfigManipulator _configManipulator;
|
||||
// private readonly IAnsiConsole _console;
|
||||
|
||||
public TemplateConfigCreator(
|
||||
ILogger log,
|
||||
IConfigTemplateGuideService templates,
|
||||
IAppPaths paths)
|
||||
{
|
||||
_log = log;
|
||||
_templates = templates;
|
||||
_paths = paths;
|
||||
// _configManipulator = configManipulator;
|
||||
// _console = console;
|
||||
}
|
||||
|
||||
public bool CanHandle(ICreateConfigSettings settings)
|
||||
{
|
||||
return settings.Templates.Any();
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public Task Create(ICreateConfigSettings settings)
|
||||
{
|
||||
_log.Debug("Creating config from templates: {Templates}", settings.Templates);
|
||||
|
||||
var matchingTemplateData = _templates.LoadTemplateData()
|
||||
.IntersectBy(settings.Templates, path => path.Id, StringComparer.CurrentCultureIgnoreCase)
|
||||
.Select(x => x.TemplateFile);
|
||||
|
||||
foreach (var templateFile in matchingTemplateData)
|
||||
{
|
||||
var destinationFile = _paths.ConfigsDirectory.File(templateFile.Name);
|
||||
|
||||
try
|
||||
{
|
||||
if (destinationFile.Exists && !settings.Force)
|
||||
{
|
||||
throw new FileExistsException($"{destinationFile} already exists");
|
||||
}
|
||||
|
||||
destinationFile.CreateParentDirectory();
|
||||
templateFile.CopyTo(destinationFile.FullName, true);
|
||||
|
||||
// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
|
||||
if (destinationFile.Exists)
|
||||
{
|
||||
_log.Information("Replacing existing file: {Path}", destinationFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
_log.Information("Created configuration file: {Path}", destinationFile);
|
||||
}
|
||||
|
||||
// -- See comment in ConfigManipulator.cs --
|
||||
// _configManipulator.LoadAndSave(templateFile, destinationFile, (instanceName, config) =>
|
||||
// {
|
||||
// _console.MarkupLineInterpolated($"Enter configuration info for instance [green]{instanceName}[/]:");
|
||||
// var baseUrl = _console.Prompt(new TextPrompt<string>("Base URL:"));
|
||||
// var apiKey = _console.Prompt(new TextPrompt<string>("API Key:"));
|
||||
// return config with
|
||||
// {
|
||||
// BaseUrl = baseUrl,
|
||||
// ApiKey = apiKey
|
||||
// };
|
||||
// });
|
||||
}
|
||||
catch (FileExistsException e)
|
||||
{
|
||||
_log.Error("Template configuration file could not be saved: {Reason}", e.AttemptedPath);
|
||||
}
|
||||
catch (FileLoadException)
|
||||
{
|
||||
// Do not log here since the origin of this exception is ConfigParser.Load(), which already has
|
||||
// sufficient logging.
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Error(e, "Unable to save configuration template file");
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.Config.Yaml;
|
||||
|
||||
namespace Recyclarr.TrashLib.Config.Parsing;
|
||||
|
||||
public class ConfigSaver
|
||||
{
|
||||
private readonly IYamlSerializerFactory _serializerFactory;
|
||||
|
||||
public ConfigSaver(IYamlSerializerFactory serializerFactory)
|
||||
{
|
||||
_serializerFactory = serializerFactory;
|
||||
}
|
||||
|
||||
public void Save(RootConfigYaml config, IFileInfo destinationFile)
|
||||
{
|
||||
var serializer = _serializerFactory.CreateSerializer();
|
||||
|
||||
destinationFile.CreateParentDirectory();
|
||||
using var stream = destinationFile.CreateText();
|
||||
serializer.Serialize(stream, config);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using System.Runtime.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Recyclarr.TrashLib.ExceptionTypes;
|
||||
|
||||
[Serializable]
|
||||
public class FatalException : Exception
|
||||
{
|
||||
public FatalException(string? message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
protected FatalException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.Extensions;
|
||||
using Recyclarr.Cli.Processors.Config;
|
||||
using Recyclarr.Cli.TestLibrary;
|
||||
using Recyclarr.Common.Extensions;
|
||||
|
||||
namespace Recyclarr.Cli.Tests.Processors.Config;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class ConfigManipulatorTest : CliIntegrationFixture
|
||||
{
|
||||
[Test]
|
||||
public void Create_file_when_no_file_already_exists()
|
||||
{
|
||||
var sut = Resolve<ConfigManipulator>();
|
||||
var src = Fs.CurrentDirectory().File("template.yml");
|
||||
var dst = Fs.CurrentDirectory().SubDir("one", "two", "three").File("config.yml");
|
||||
|
||||
const string yamlData = @"
|
||||
sonarr:
|
||||
instance1:
|
||||
base_url: http://localhost:80
|
||||
api_key: 123abc
|
||||
";
|
||||
|
||||
Fs.AddFile(src, new MockFileData(yamlData));
|
||||
|
||||
sut.LoadAndSave(src, dst, (_, yaml) => yaml);
|
||||
|
||||
Fs.AllFiles.Should().Contain(dst.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throw_on_invalid_yaml()
|
||||
{
|
||||
var sut = Resolve<ConfigManipulator>();
|
||||
var src = Fs.CurrentDirectory().File("template.yml");
|
||||
var dst = Fs.CurrentDirectory().File("config.yml");
|
||||
|
||||
const string yamlData = @"
|
||||
sonarr:
|
||||
instance1:
|
||||
invalid: yaml
|
||||
";
|
||||
|
||||
Fs.AddFile(src, new MockFileData(yamlData));
|
||||
|
||||
var act = () => sut.LoadAndSave(src, dst, (_, yaml) => yaml);
|
||||
|
||||
act.Should().Throw<FileLoadException>();
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.Extensions;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Cli.Processors.Config;
|
||||
using Recyclarr.Cli.TestLibrary;
|
||||
using Recyclarr.TestLibrary.AutoFixture;
|
||||
using Recyclarr.TrashLib.Config;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Repo;
|
||||
|
||||
namespace Recyclarr.Cli.Tests.Processors.Config;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class TemplateConfigCreatorTest : CliIntegrationFixture
|
||||
{
|
||||
[Test, AutoMockData]
|
||||
public void Can_handle_returns_true_with_templates(
|
||||
ICreateConfigSettings settings,
|
||||
TemplateConfigCreator sut)
|
||||
{
|
||||
settings.Templates.Returns(new[] {"template1"});
|
||||
var result = sut.CanHandle(settings);
|
||||
result.Should().Be(true);
|
||||
}
|
||||
|
||||
[Test, AutoMockData]
|
||||
public void Can_handle_returns_false_with_no_templates(
|
||||
ICreateConfigSettings settings,
|
||||
TemplateConfigCreator sut)
|
||||
{
|
||||
settings.Templates.Returns(Array.Empty<string>());
|
||||
var result = sut.CanHandle(settings);
|
||||
result.Should().Be(false);
|
||||
}
|
||||
|
||||
[Test, AutoMockData]
|
||||
public void Throw_when_file_exists_and_not_forced(
|
||||
[Frozen] IConfigTemplateGuideService templates,
|
||||
MockFileSystem fs,
|
||||
ICreateConfigSettings settings,
|
||||
TemplateConfigCreator sut)
|
||||
{
|
||||
templates.LoadTemplateData().Returns(new[]
|
||||
{
|
||||
new TemplatePath
|
||||
{
|
||||
Id = "template1",
|
||||
TemplateFile = fs.CurrentDirectory().File("template-file1.yml"),
|
||||
Service = SupportedServices.Radarr
|
||||
}
|
||||
});
|
||||
|
||||
settings.Force.Returns(false);
|
||||
settings.Templates.Returns(new[]
|
||||
{
|
||||
"template1"
|
||||
});
|
||||
|
||||
var act = () => sut.Create(settings);
|
||||
|
||||
act.Should().ThrowAsync<FileExistsException>();
|
||||
}
|
||||
|
||||
[Test, AutoMockData]
|
||||
public void No_throw_when_file_exists_and_forced(
|
||||
[Frozen] IConfigTemplateGuideService templates,
|
||||
MockFileSystem fs,
|
||||
ICreateConfigSettings settings,
|
||||
TemplateConfigCreator sut)
|
||||
{
|
||||
templates.LoadTemplateData().Returns(new[]
|
||||
{
|
||||
new TemplatePath
|
||||
{
|
||||
Id = "template1",
|
||||
TemplateFile = fs.CurrentDirectory().File("template-file1.yml"),
|
||||
Service = SupportedServices.Radarr
|
||||
}
|
||||
});
|
||||
|
||||
settings.Force.Returns(true);
|
||||
settings.Templates.Returns(new[]
|
||||
{
|
||||
"template1"
|
||||
});
|
||||
|
||||
var act = () => sut.Create(settings);
|
||||
|
||||
act.Should().NotThrowAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Template_id_matching_works()
|
||||
{
|
||||
const string templatesJson = @"
|
||||
{
|
||||
'radarr': [
|
||||
{
|
||||
'template': 'template-file1.yml',
|
||||
'id': 'template1'
|
||||
}
|
||||
],
|
||||
'sonarr': [
|
||||
{
|
||||
'template': 'template-file2.yml',
|
||||
'id': 'template2'
|
||||
},
|
||||
{
|
||||
'template': 'template-file3.yml',
|
||||
'id': 'template3'
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
var repo = Resolve<IConfigTemplatesRepo>();
|
||||
Fs.AddFile(repo.Path.File("templates.json"), new MockFileData(templatesJson));
|
||||
Fs.AddEmptyFile(repo.Path.File("template-file1.yml"));
|
||||
Fs.AddEmptyFile(repo.Path.File("template-file2.yml"));
|
||||
// This one shouldn't show up in the result because the user didn't ask for it
|
||||
Fs.AddEmptyFile(repo.Path.File("template-file3.yml"));
|
||||
|
||||
var settings = Substitute.For<ICreateConfigSettings>();
|
||||
settings.Templates.Returns(new[]
|
||||
{
|
||||
"template1",
|
||||
"template2",
|
||||
// This one shouldn't show up in the results because:
|
||||
// User specified it, but no template file exists for it.
|
||||
"template4"
|
||||
});
|
||||
|
||||
var sut = Resolve<TemplateConfigCreator>();
|
||||
sut.Create(settings);
|
||||
|
||||
Fs.AllFiles.Should().Contain(new[]
|
||||
{
|
||||
Paths.ConfigsDirectory.File("template-file1.yml").FullName,
|
||||
Paths.ConfigsDirectory.File("template-file2.yml").FullName
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
|
||||
namespace Recyclarr.TestLibrary.AutoFixture;
|
||||
|
||||
// Based on the answer here: https://stackoverflow.com/a/16735551/157971
|
||||
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)]
|
||||
public sealed class CustomizeWithAttribute : CustomizeAttribute
|
||||
{
|
||||
public Type CustomizationType { get; }
|
||||
|
||||
public CustomizeWithAttribute(Type customizationType)
|
||||
{
|
||||
if (customizationType == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(customizationType));
|
||||
}
|
||||
|
||||
if (!typeof(ICustomization).IsAssignableFrom(customizationType))
|
||||
{
|
||||
throw new ArgumentException("Type needs to implement ICustomization");
|
||||
}
|
||||
|
||||
CustomizationType = customizationType;
|
||||
}
|
||||
|
||||
public override ICustomization? GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return (ICustomization?) Activator.CreateInstance(CustomizationType);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.TestCorrelator;
|
||||
|
||||
namespace Recyclarr.TestLibrary;
|
||||
|
||||
public sealed class TestableLogger : ILogger, IDisposable
|
||||
{
|
||||
private readonly Logger _log;
|
||||
private ITestCorrelatorContext _logContext;
|
||||
|
||||
public TestableLogger()
|
||||
{
|
||||
_log = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(LogEventLevel.Verbose)
|
||||
.WriteTo.TestCorrelator()
|
||||
.WriteTo.Console()
|
||||
.CreateLogger();
|
||||
|
||||
_logContext = TestCorrelator.CreateContext();
|
||||
}
|
||||
|
||||
public void Write(LogEvent logEvent)
|
||||
{
|
||||
_log.Write(logEvent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_logContext.Dispose();
|
||||
_log.Dispose();
|
||||
}
|
||||
|
||||
public void ResetCapturedLogs()
|
||||
{
|
||||
_logContext.Dispose();
|
||||
_logContext = TestCorrelator.CreateContext();
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetRenderedMessages()
|
||||
{
|
||||
return TestCorrelator.GetLogEventsFromContextGuid(_logContext.Guid)
|
||||
.Select(x => x.RenderMessage());
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.Extensions;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.Config.Parsing;
|
||||
using Recyclarr.TrashLib.TestLibrary;
|
||||
|
||||
namespace Recyclarr.TrashLib.Tests.Config.Parsing;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class ConfigSaverTest : TrashLibIntegrationFixture
|
||||
{
|
||||
[Test]
|
||||
public void Replace_file_when_already_exists()
|
||||
{
|
||||
var sut = Resolve<ConfigSaver>();
|
||||
var config = new RootConfigYaml
|
||||
{
|
||||
Radarr = new Dictionary<string, RadarrConfigYaml>
|
||||
{
|
||||
{
|
||||
"instance1", new RadarrConfigYaml
|
||||
{
|
||||
ApiKey = "apikey"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var destFile = Fs.CurrentDirectory().File("config.yml");
|
||||
Fs.AddEmptyFile(destFile);
|
||||
|
||||
sut.Save(config, destFile);
|
||||
|
||||
Fs.GetFile(destFile).TextContents.Should().Contain("apikey");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_intermediate_directories()
|
||||
{
|
||||
var sut = Resolve<ConfigSaver>();
|
||||
|
||||
var config = new RootConfigYaml
|
||||
{
|
||||
Radarr = new Dictionary<string, RadarrConfigYaml>
|
||||
{
|
||||
{
|
||||
"instance1", new RadarrConfigYaml
|
||||
{
|
||||
ApiKey = "apikey",
|
||||
BaseUrl = "http://baseurl.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var destFile = Fs.CurrentDirectory().SubDir("one", "two", "three").File("config.yml");
|
||||
|
||||
sut.Save(config, destFile);
|
||||
|
||||
var expectedYaml = @"
|
||||
radarr:
|
||||
instance1:
|
||||
api_key: apikey
|
||||
base_url: http://baseurl.com
|
||||
".TrimStart();
|
||||
|
||||
var expectedFile = Fs.GetFile(destFile);
|
||||
expectedFile.Should().NotBeNull();
|
||||
expectedFile.TextContents.Should().Be(expectedYaml);
|
||||
}
|
||||
}
|
Loading…
Reference in new issue