For deleting one or many custom formats from a specific Sonarr or Radarr service.json-serializing-nullable-fields-issue
parent
a84c8a0efc
commit
f6465316d2
@ -0,0 +1,20 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="delete custom-formats" 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="delete custom-formats radarr_develop --all --preview" />
|
||||
<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>
|
@ -1,6 +1,69 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Cli.Processors;
|
||||
using Recyclarr.Cli.Processors.Delete;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Recyclarr.Cli.Console.Commands;
|
||||
|
||||
public class DeleteCustomFormatsCommand
|
||||
[Description("Delete things from services like Radarr & Sonarr")]
|
||||
[UsedImplicitly]
|
||||
public class DeleteCustomFormatsCommand : AsyncCommand<DeleteCustomFormatsCommand.CliSettings>
|
||||
{
|
||||
|
||||
private readonly IDeleteCustomFormatsProcessor _processor;
|
||||
private readonly ConsoleExceptionHandler _exceptionHandler;
|
||||
|
||||
[UsedImplicitly]
|
||||
[SuppressMessage("Design", "CA1034:Nested types should not be visible")]
|
||||
[SuppressMessage("Performance", "CA1819:Properties should not return arrays",
|
||||
Justification = "Spectre.Console requires it")]
|
||||
public class CliSettings : ServiceCommandSettings, IDeleteCustomFormatSettings
|
||||
{
|
||||
[CommandArgument(0, "<instance_name>")]
|
||||
[Description("The name of the instance to delete CFs from.")]
|
||||
public string InstanceName { get; init; } = "";
|
||||
|
||||
[CommandArgument(0, "[cf_names]")]
|
||||
[Description("One or more custom format names to delete. Optional only if `--all` is used.")]
|
||||
public string[] CustomFormatNamesOption { get; init; } = Array.Empty<string>();
|
||||
public IReadOnlyCollection<string> CustomFormatNames => CustomFormatNamesOption;
|
||||
|
||||
[CommandOption("-a|--all")]
|
||||
[Description("Delete ALL custom formats.")]
|
||||
public bool All { get; init; } = false;
|
||||
|
||||
[CommandOption("-f|--force")]
|
||||
[Description("Perform the delete operation with NO confirmation prompt.")]
|
||||
public bool Force { get; init; } = false;
|
||||
|
||||
[CommandOption("-p|--preview")]
|
||||
[Description("Preview what custom formats will be deleted without actually deleting them.")]
|
||||
public bool Preview { get; init; } = false;
|
||||
}
|
||||
|
||||
public DeleteCustomFormatsCommand(
|
||||
IDeleteCustomFormatsProcessor processor,
|
||||
ConsoleExceptionHandler exceptionHandler)
|
||||
{
|
||||
_processor = processor;
|
||||
_exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, CliSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _processor.Process(settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await _exceptionHandler.HandleException(e);
|
||||
return (int) ExitStatus.Failed;
|
||||
}
|
||||
|
||||
return (int) ExitStatus.Succeeded;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
namespace Recyclarr.Cli.Console.Settings;
|
||||
|
||||
public interface IDeleteCustomFormatSettings
|
||||
{
|
||||
public string InstanceName { get; }
|
||||
public IReadOnlyCollection<string> CustomFormatNames { get; }
|
||||
public bool All { get; }
|
||||
public bool Force { get; }
|
||||
bool Preview { get; }
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
using Flurl.Http;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.Config.Parsing.ErrorHandling;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Http;
|
||||
using Recyclarr.TrashLib.Repo.VersionControl;
|
||||
|
||||
namespace Recyclarr.Cli.Processors;
|
||||
|
||||
public class ConsoleExceptionHandler
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
|
||||
public ConsoleExceptionHandler(ILogger log)
|
||||
{
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Task HandleException(Exception sourceException)
|
||||
{
|
||||
switch (sourceException)
|
||||
{
|
||||
case GitCmdException e:
|
||||
_log.Error(e, "Non-zero exit code {ExitCode} while executing Git command: {Error}",
|
||||
e.ExitCode, e.Error);
|
||||
break;
|
||||
|
||||
case FlurlHttpException e:
|
||||
_log.Error("HTTP error: {Message}", e.SanitizedExceptionMessage());
|
||||
foreach (var error in await GetValidationErrorsAsync(e))
|
||||
{
|
||||
_log.Error("Reason: {Error}", error);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NoConfigurationFilesException:
|
||||
_log.Error("No configuration files found");
|
||||
break;
|
||||
|
||||
case InvalidInstancesException e:
|
||||
_log.Error("The following instances do not exist: {Names}", e.InstanceNames);
|
||||
break;
|
||||
|
||||
case SplitInstancesException e:
|
||||
_log.Error("The following configs share the same `base_url`, which isn't allowed: {Instances}",
|
||||
e.InstanceNames);
|
||||
_log.Error(
|
||||
"Consolidate the config files manually to fix. " +
|
||||
"See: https://recyclarr.dev/wiki/yaml/config-examples/#merge-single-instance");
|
||||
break;
|
||||
|
||||
case InvalidConfigurationFilesException e:
|
||||
_log.Error("Manually-specified configuration files do not exist: {Files}", e.InvalidFiles);
|
||||
break;
|
||||
|
||||
case CommandException e:
|
||||
_log.Error(e.Message);
|
||||
break;
|
||||
|
||||
// This handles non-deterministic/unexpected exceptions.
|
||||
default:
|
||||
throw sourceException;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IReadOnlyCollection<string>> GetValidationErrorsAsync(FlurlHttpException e)
|
||||
{
|
||||
var response = await e.GetResponseJsonAsync<List<dynamic>>();
|
||||
if (response is null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return response
|
||||
.Select(x => (string) x.errorMessage)
|
||||
.NotNull(x => !string.IsNullOrEmpty(x))
|
||||
.ToList();
|
||||
}
|
||||
}
|
@ -0,0 +1,179 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Cli.Pipelines.CustomFormat.Api;
|
||||
using Recyclarr.TrashLib.Config;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.Models;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Delete;
|
||||
|
||||
public class DeleteCustomFormatsProcessor : IDeleteCustomFormatsProcessor
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly IAnsiConsole _console;
|
||||
private readonly ICustomFormatService _api;
|
||||
private readonly IConfigurationRegistry _configRegistry;
|
||||
|
||||
public DeleteCustomFormatsProcessor(
|
||||
ILogger log,
|
||||
IAnsiConsole console,
|
||||
ICustomFormatService api,
|
||||
IConfigurationRegistry configRegistry)
|
||||
{
|
||||
_log = log;
|
||||
_console = console;
|
||||
_api = api;
|
||||
_configRegistry = configRegistry;
|
||||
}
|
||||
|
||||
public async Task Process(IDeleteCustomFormatSettings settings)
|
||||
{
|
||||
var config = GetTargetConfig(settings);
|
||||
var cfs = await ObtainCustomFormats(config);
|
||||
|
||||
if (!settings.All)
|
||||
{
|
||||
if (!settings.CustomFormatNames.Any())
|
||||
{
|
||||
throw new CommandException("Custom format names must be specified if the `--all` option is not used.");
|
||||
}
|
||||
|
||||
cfs = ProcessManuallySpecifiedFormats(settings, cfs);
|
||||
}
|
||||
|
||||
if (!cfs.Any())
|
||||
{
|
||||
_console.MarkupLine("[yellow]Done[/]: No custom formats found or specified to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
PrintPreview(cfs);
|
||||
|
||||
if (settings.Preview)
|
||||
{
|
||||
_console.MarkupLine("This is a preview! [u]No actual deletions will be performed.[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settings.Force &&
|
||||
!_console.Confirm("\nAre you sure you want to [bold red]permanently delete[/] the above custom formats?"))
|
||||
{
|
||||
_console.WriteLine("Aborted!");
|
||||
return;
|
||||
}
|
||||
|
||||
await DeleteCustomFormats(cfs, config);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
|
||||
private async Task DeleteCustomFormats(ICollection<CustomFormatData> cfs, IServiceConfiguration config)
|
||||
{
|
||||
await _console.Progress().StartAsync(async ctx =>
|
||||
{
|
||||
var task = ctx.AddTask("Deleting Custom Formats").MaxValue(cfs.Count);
|
||||
|
||||
var options = new ParallelOptions {MaxDegreeOfParallelism = 8};
|
||||
await Parallel.ForEachAsync(cfs, options, async (cf, token) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _api.DeleteCustomFormat(config, cf.Id, token);
|
||||
_log.Debug("Deleted {Name}", cf.Name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.Debug(e, "Failed to delete CF");
|
||||
_console.WriteLine($"Failed to delete CF: {cf.Name}");
|
||||
}
|
||||
|
||||
task.Increment(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IList<CustomFormatData>> ObtainCustomFormats(IServiceConfiguration config)
|
||||
{
|
||||
IList<CustomFormatData> cfs = new List<CustomFormatData>();
|
||||
|
||||
await _console.Status().StartAsync("Obtaining custom formats...", async _ =>
|
||||
{
|
||||
cfs = await _api.GetCustomFormats(config);
|
||||
});
|
||||
|
||||
return cfs;
|
||||
}
|
||||
|
||||
private IList<CustomFormatData> ProcessManuallySpecifiedFormats(
|
||||
IDeleteCustomFormatSettings settings,
|
||||
IList<CustomFormatData> cfs)
|
||||
{
|
||||
ILookup<bool, (string Name, IEnumerable<CustomFormatData> Cfs)> result = settings.CustomFormatNames
|
||||
.GroupJoin(cfs,
|
||||
x => x,
|
||||
x => x.Name,
|
||||
(x, y) => (Name: x, Cf: y),
|
||||
StringComparer.InvariantCultureIgnoreCase)
|
||||
.ToLookup(x => x.Cf.Any());
|
||||
|
||||
// 'false' means there were no CFs matched to this CF name
|
||||
if (result[false].Any())
|
||||
{
|
||||
var cfNames = result[false].Select(x => x.Name).ToList();
|
||||
_log.Debug("Unmatched CFs: {Names}", cfNames);
|
||||
foreach (var name in cfNames)
|
||||
{
|
||||
_console.MarkupLineInterpolated($"[yellow]Warning[/]: Unmatched CF Name: [teal]{name}[/]");
|
||||
}
|
||||
}
|
||||
|
||||
// 'true' represents CFs that match names provided in user-input (if provided)
|
||||
cfs = result[true].SelectMany(x => x.Cfs).ToList();
|
||||
return cfs;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "CoVariantArrayConversion")]
|
||||
private void PrintPreview(ICollection<CustomFormatData> cfs)
|
||||
{
|
||||
_console.MarkupLine("The following custom formats will be [bold red]DELETED[/]:");
|
||||
_console.WriteLine();
|
||||
|
||||
var cfNames = cfs
|
||||
.Select(x => x.Name)
|
||||
.Order(StringComparer.InvariantCultureIgnoreCase)
|
||||
.Chunk(Math.Max(15, cfs.Count / 3)) // Minimum row size is 15 for the table
|
||||
.ToList();
|
||||
|
||||
var grid = new Grid().AddColumns(cfNames.Count);
|
||||
|
||||
foreach (var rowItems in cfNames.Transpose())
|
||||
{
|
||||
grid.AddRow(rowItems
|
||||
.Select(x => Markup.FromInterpolated($"[bold white]{x}[/]"))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
_console.Write(grid);
|
||||
_console.WriteLine();
|
||||
}
|
||||
|
||||
private IServiceConfiguration GetTargetConfig(IDeleteCustomFormatSettings settings)
|
||||
{
|
||||
var configs = _configRegistry.FindAndLoadConfigs(new ConfigFilterCriteria
|
||||
{
|
||||
Instances = new[] {settings.InstanceName}
|
||||
});
|
||||
|
||||
switch (configs.Count)
|
||||
{
|
||||
case 0:
|
||||
throw new ArgumentException($"No configuration found with name: {settings.InstanceName}");
|
||||
|
||||
case > 1:
|
||||
throw new ArgumentException($"More than one instance found with this name: {settings.InstanceName}");
|
||||
}
|
||||
|
||||
return configs.Single();
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
|
||||
namespace Recyclarr.Cli.Processors.Delete;
|
||||
|
||||
public interface IDeleteCustomFormatsProcessor
|
||||
{
|
||||
Task Process(IDeleteCustomFormatSettings settings);
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using Recyclarr.Cli.Console.Settings;
|
||||
using Recyclarr.Common.Extensions;
|
||||
using Recyclarr.TrashLib.Config;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
|
||||
namespace Recyclarr.Cli.Processors;
|
||||
|
||||
public static class ProcessorExtensions
|
||||
{
|
||||
public static IEnumerable<IServiceConfiguration> GetConfigsBasedOnSettings(
|
||||
this IEnumerable<IServiceConfiguration> configs,
|
||||
ISyncSettings settings)
|
||||
{
|
||||
// later, if we filter by "operation type" (e.g. release profiles, CFs, quality sizes) it's just another
|
||||
// ".Where()" in the LINQ expression below.
|
||||
return configs.GetConfigsOfType(settings.Service)
|
||||
.Where(x => settings.Instances.IsEmpty() ||
|
||||
settings.Instances!.Any(y => y.EqualsIgnoreCase(x.InstanceName)));
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetSplitInstances(this IEnumerable<IServiceConfiguration> configs)
|
||||
{
|
||||
return configs
|
||||
.GroupBy(x => x.BaseUrl)
|
||||
.Where(x => x.Count() > 1)
|
||||
.SelectMany(x => x.Select(y => y.InstanceName));
|
||||
}
|
||||
|
||||
public static IEnumerable<string> GetInvalidInstanceNames(
|
||||
this ISyncSettings settings,
|
||||
IEnumerable<IServiceConfiguration> configs)
|
||||
{
|
||||
if (settings.Instances is null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var configInstances = configs.Select(x => x.InstanceName).ToList();
|
||||
return settings.Instances
|
||||
.Where(x => !configInstances.Contains(x, StringComparer.InvariantCultureIgnoreCase));
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Recyclarr.TrashLib.Config;
|
||||
|
||||
public record ConfigFilterCriteria
|
||||
{
|
||||
public IReadOnlyCollection<string>? ManualConfigFiles { get; init; }
|
||||
public SupportedServices? Service { get; init; }
|
||||
public IReadOnlyCollection<string>? Instances { get; init; }
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.TrashLib.Config.Parsing;
|
||||
using Recyclarr.TrashLib.Config.Parsing.ErrorHandling;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
|
||||
namespace Recyclarr.TrashLib.Config;
|
||||
|
||||
public class ConfigurationRegistry : IConfigurationRegistry
|
||||
{
|
||||
private readonly IConfigurationLoader _loader;
|
||||
private readonly IConfigurationFinder _finder;
|
||||
private readonly IFileSystem _fs;
|
||||
|
||||
public ConfigurationRegistry(IConfigurationLoader loader, IConfigurationFinder finder, IFileSystem fs)
|
||||
{
|
||||
_loader = loader;
|
||||
_finder = finder;
|
||||
_fs = fs;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<IServiceConfiguration> FindAndLoadConfigs(ConfigFilterCriteria? filterCriteria = null)
|
||||
{
|
||||
filterCriteria ??= new ConfigFilterCriteria();
|
||||
|
||||
var manualConfigs = filterCriteria.ManualConfigFiles;
|
||||
var configs = manualConfigs is not null && manualConfigs.Any()
|
||||
? PrepareManualConfigs(manualConfigs)
|
||||
: _finder.GetConfigFiles();
|
||||
|
||||
return LoadAndFilterConfigs(configs, filterCriteria).ToList();
|
||||
}
|
||||
|
||||
private IReadOnlyCollection<IFileInfo> PrepareManualConfigs(IEnumerable<string> manualConfigs)
|
||||
{
|
||||
var configFiles = manualConfigs
|
||||
.Select(x => _fs.FileInfo.New(x))
|
||||
.ToLookup(x => x.Exists);
|
||||
|
||||
if (configFiles[false].Any())
|
||||
{
|
||||
throw new InvalidConfigurationFilesException(configFiles[false].ToList());
|
||||
}
|
||||
|
||||
return configFiles[true].ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<IServiceConfiguration> LoadAndFilterConfigs(
|
||||
IEnumerable<IFileInfo> configs,
|
||||
ConfigFilterCriteria filterCriteria)
|
||||
{
|
||||
var loadedConfigs = configs.SelectMany(x => _loader.Load(x)).ToList();
|
||||
|
||||
var invalidInstances = loadedConfigs.GetInvalidInstanceNames(filterCriteria).ToList();
|
||||
if (invalidInstances.Any())
|
||||
{
|
||||
throw new InvalidInstancesException(invalidInstances);
|
||||
}
|
||||
|
||||
var splitInstances = loadedConfigs.GetSplitInstances().ToList();
|
||||
if (splitInstances.Any())
|
||||
{
|
||||
throw new SplitInstancesException(splitInstances);
|
||||
}
|
||||
|
||||
return loadedConfigs.GetConfigsBasedOnSettings(filterCriteria);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
|
||||
namespace Recyclarr.TrashLib.Config;
|
||||
|
||||
public interface IConfigurationRegistry
|
||||
{
|
||||
IReadOnlyCollection<IServiceConfiguration> FindAndLoadConfigs(ConfigFilterCriteria? filterCriteria = null);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Recyclarr.TrashLib.Config.Parsing.ErrorHandling;
|
||||
|
||||
public class InvalidConfigurationFilesException : Exception
|
||||
{
|
||||
public IReadOnlyCollection<IFileInfo> InvalidFiles { get; }
|
||||
|
||||
public InvalidConfigurationFilesException(IReadOnlyCollection<IFileInfo> invalidFiles)
|
||||
{
|
||||
InvalidFiles = invalidFiles;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace Recyclarr.TrashLib.ExceptionTypes;
|
||||
|
||||
public class CommandException : Exception
|
||||
{
|
||||
public CommandException(string? message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
using Recyclarr.TrashLib.Config;
|
||||
using Recyclarr.TrashLib.Config.Parsing.ErrorHandling;
|
||||
using Recyclarr.TrashLib.Config.Services;
|
||||
using Recyclarr.TrashLib.ExceptionTypes;
|
||||
using Recyclarr.TrashLib.TestLibrary;
|
||||
|
||||
namespace Recyclarr.TrashLib.Tests.Config;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public class ConfigurationRegistryTest : TrashLibIntegrationFixture
|
||||
{
|
||||
[Test]
|
||||
public void Use_explicit_paths_instead_of_default()
|
||||
{
|
||||
var sut = Resolve<ConfigurationRegistry>();
|
||||
|
||||
Fs.AddFile("manual.yml", new MockFileData(
|
||||
"""
|
||||
radarr:
|
||||
instance1:
|
||||
base_url: http://localhost:7878
|
||||
api_key: asdf
|
||||
"""));
|
||||
|
||||
var result = sut.FindAndLoadConfigs(new ConfigFilterCriteria
|
||||
{
|
||||
ManualConfigFiles = new[] {"manual.yml"}
|
||||
});
|
||||
|
||||
result.Should().BeEquivalentTo(new[]
|
||||
{
|
||||
new RadarrConfiguration
|
||||
{
|
||||
BaseUrl = new Uri("http://localhost:7878"),
|
||||
ApiKey = "asdf",
|
||||
InstanceName = "instance1"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throw_on_invalid_config_files()
|
||||
{
|
||||
var sut = Resolve<ConfigurationRegistry>();
|
||||
|
||||
var act = () => sut.FindAndLoadConfigs(new ConfigFilterCriteria
|
||||
{
|
||||
ManualConfigFiles = new[] {"manual.yml"}
|
||||
});
|
||||
|
||||
act.Should().ThrowExactly<InvalidConfigurationFilesException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throw_on_invalid_instances()
|
||||
{
|
||||
var sut = Resolve<ConfigurationRegistry>();
|
||||
|
||||
Fs.AddFile("manual.yml", new MockFileData(
|
||||
"""
|
||||
radarr:
|
||||
instance1:
|
||||
base_url: http://localhost:7878
|
||||
api_key: asdf
|
||||
"""));
|
||||
|
||||
var act = () => sut.FindAndLoadConfigs(new ConfigFilterCriteria
|
||||
{
|
||||
ManualConfigFiles = new[] {"manual.yml"},
|
||||
Instances = new[] {"instance1", "instance2"}
|
||||
});
|
||||
|
||||
act.Should().ThrowExactly<InvalidInstancesException>()
|
||||
.Which.InstanceNames.Should().BeEquivalentTo("instance2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Throw_on_split_instances()
|
||||
{
|
||||
var sut = Resolve<ConfigurationRegistry>();
|
||||
|
||||
Fs.AddFile("manual.yml", new MockFileData(
|
||||
"""
|
||||
radarr:
|
||||
instance1:
|
||||
base_url: http://localhost:7878
|
||||
api_key: asdf
|
||||
instance2:
|
||||
base_url: http://localhost:7878
|
||||
api_key: asdf
|
||||
"""));
|
||||
|
||||
var act = () => sut.FindAndLoadConfigs(new ConfigFilterCriteria
|
||||
{
|
||||
ManualConfigFiles = new[] {"manual.yml"}
|
||||
});
|
||||
|
||||
act.Should().ThrowExactly<SplitInstancesException>()
|
||||
.Which.InstanceNames.Should().BeEquivalentTo("instance1", "instance2");
|
||||
}
|
||||
}
|
Loading…
Reference in new issue