feat: Renamed CF `quality_profiles` to `assign_scores_to`

pull/302/head
Robert Dailey 4 months ago
parent cc23f2e653
commit e2cff7d9fa

@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- The node `quality_profiles` under `custom_formats` was renamed to `assign_scores_to` to
disambiguate it from the top-level `quality_profiles`. The old name is deprecated until the next
major release. See [here][qp_rename] for details.
[qp_rename]: https://recyclarr.dev/wiki/upgrade-guide/v8.0/#assign-scores-to
### Fixed
- Incorrect URLs were fixed in the local starter config template.

@ -17,7 +17,7 @@
"type": "string"
}
},
"quality_profiles": {
"assign_scores_to": {
"type": ["null", "array"],
"description": "One or more quality profiles to update with the scores from the specified custom formats.",
"items": {

@ -8,6 +8,6 @@ public class ProcessedConfigData
public ICollection<CustomFormatData> CustomFormats { get; init; }
= new List<CustomFormatData>();
public ICollection<QualityProfileScoreConfig> QualityProfiles { get; init; }
= new List<QualityProfileScoreConfig>();
public ICollection<AssignScoresToConfig> QualityProfiles { get; init; }
= new List<AssignScoresToConfig>();
}

@ -16,7 +16,7 @@ public class QualityProfileConfigPhase(ILogger log, ProcessedCustomFormatCache c
// 2. For each quality profile score config in that CF group
// 3. For each CF in the group above, match it to a Guide CF object and pair it with the quality profile config
var profileAndCfs = config.CustomFormats
.SelectMany(x => x.QualityProfiles
.SelectMany(x => x.AssignScoresTo
.Select(y => (Profile: y, x.TrashIds)))
.SelectMany(x => x.TrashIds
.Select(cache.LookupByTrashId)
@ -74,7 +74,7 @@ public class QualityProfileConfigPhase(ILogger log, ProcessedCustomFormatCache c
private void AddCustomFormatScoreData(
ProcessedQualityProfileData profile,
QualityProfileScoreConfig scoreConfig,
AssignScoresToConfig scoreConfig,
CustomFormatData cf)
{
var existingScoreData = profile.CfScores;
@ -109,7 +109,7 @@ public class QualityProfileConfigPhase(ILogger log, ProcessedCustomFormatCache c
private int? DetermineScore(
QualityProfileConfig profile,
QualityProfileScoreConfig scoreConfig,
AssignScoresToConfig scoreConfig,
CustomFormatData cf)
{
if (scoreConfig.Score is not null)

@ -1,9 +1,11 @@
using Autofac;
using Autofac.Extras.Ordering;
using AutoMapper.Contrib.Autofac.DependencyInjection;
using FluentValidation;
using Recyclarr.Config.Parsing;
using Recyclarr.Config.Parsing.PostProcessing;
using Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
using Recyclarr.Config.Parsing.PostProcessing.Deprecations;
using Recyclarr.Config.Secrets;
using Recyclarr.Yaml;
@ -21,8 +23,6 @@ public class ConfigAutofacModule : Module
builder.RegisterType<SecretsProvider>().As<ISecretsProvider>().SingleInstance();
builder.RegisterType<YamlIncludeResolver>().As<IYamlIncludeResolver>();
builder.RegisterType<ConfigIncludeProcessor>().As<IIncludeProcessor>();
builder.RegisterType<TemplateIncludeProcessor>().As<IIncludeProcessor>();
builder.RegisterType<ConfigurationRegistry>().As<IConfigurationRegistry>();
builder.RegisterType<ConfigurationLoader>().As<IConfigurationLoader>();
builder.RegisterType<ConfigurationFinder>().As<IConfigurationFinder>();
@ -31,9 +31,26 @@ public class ConfigAutofacModule : Module
builder.RegisterType<ConfigSaver>();
builder.RegisterType<ConfigurationScopeFactory>();
// Keyed include processors
builder.RegisterType<ConfigIncludeProcessor>().Keyed<IIncludeProcessor>(typeof(ConfigYamlInclude));
builder.RegisterType<TemplateIncludeProcessor>().Keyed<IIncludeProcessor>(typeof(TemplateYamlInclude));
// Config Post Processors
builder.RegisterType<ImplicitUrlAndKeyPostProcessor>().As<IConfigPostProcessor>();
builder.RegisterType<IncludePostProcessor>().As<IConfigPostProcessor>();
builder.RegisterTypes(
// Order-sensitive!
typeof(ConfigDeprecationPostProcessor),
typeof(ImplicitUrlAndKeyPostProcessor),
typeof(IncludePostProcessor))
.As<IConfigPostProcessor>()
.OrderByRegistration();
// Config Deprecations
builder.RegisterType<ConfigDeprecations>();
builder.RegisterTypes(
// Order-sensitive!
typeof(CfQualityProfilesDeprecationCheck))
.As<IConfigDeprecationCheck>()
.OrderByRegistration();
RegisterValidators(builder);
}

@ -28,12 +28,12 @@ public record CustomFormatConfig
{
public ICollection<string> TrashIds { get; init; } = new List<string>();
public ICollection<QualityProfileScoreConfig> QualityProfiles { get; init; } =
new List<QualityProfileScoreConfig>();
public ICollection<AssignScoresToConfig> AssignScoresTo { get; init; } =
new List<AssignScoresToConfig>();
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public record QualityProfileScoreConfig
public record AssignScoresToConfig
{
public string Name { get; init; } = "";
public int? Score { get; init; }

@ -17,6 +17,7 @@ public record QualityScoreConfigYaml
public record CustomFormatConfigYaml
{
public IReadOnlyCollection<string>? TrashIds { get; init; }
public IReadOnlyCollection<QualityScoreConfigYaml>? AssignScoresTo { get; init; }
public IReadOnlyCollection<QualityScoreConfigYaml>? QualityProfiles { get; init; }
}

@ -42,7 +42,7 @@ public class CustomFormatConfigYamlValidator : AbstractValidator<CustomFormatCon
{
public CustomFormatConfigYamlValidator()
{
RuleForEach(x => x.QualityProfiles)
RuleForEach(x => x.AssignScoresTo)
.SetValidator(new QualityScoreConfigYamlValidator());
}
}

@ -9,7 +9,7 @@ public class ConfigYamlMapperProfile : Profile
{
public ConfigYamlMapperProfile()
{
CreateMap<QualityScoreConfigYaml, QualityProfileScoreConfig>();
CreateMap<QualityScoreConfigYaml, AssignScoresToConfig>();
CreateMap<CustomFormatConfigYaml, CustomFormatConfig>();
CreateMap<QualitySizeConfigYaml, QualityDefinitionConfig>();
CreateMap<ResetUnmatchedScoresConfigYaml, ResetUnmatchedScoresConfig>();

@ -12,7 +12,7 @@ public class ConfigurationLoader(
ConfigParser parser,
IMapper mapper,
ConfigValidationExecutor validator,
IEnumerable<IConfigPostProcessor> postProcessors)
IOrderedEnumerable<IConfigPostProcessor> postProcessors)
: IConfigurationLoader
{
public IReadOnlyCollection<IServiceConfiguration> Load(IFileInfo file)

@ -0,0 +1,17 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Config.Parsing.PostProcessing.Deprecations;
namespace Recyclarr.Config.Parsing.PostProcessing;
public class ConfigDeprecationPostProcessor(ConfigDeprecations deprecations) : IConfigPostProcessor
{
[SuppressMessage("ReSharper", "WithExpressionModifiesAllMembers")]
public RootConfigYaml Process(RootConfigYaml config)
{
return config with
{
Radarr = config.Radarr?.ToDictionary(x => x.Key, x => deprecations.CheckAndTransform(x.Value)),
Sonarr = config.Sonarr?.ToDictionary(x => x.Key, x => deprecations.CheckAndTransform(x.Value))
};
}
}

@ -6,9 +6,22 @@ namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
public class ConfigIncludeProcessor(IFileSystem fs, IAppPaths paths, ILogger log) : IIncludeProcessor
{
public bool CanProcess(IYamlInclude includeDirective)
public IFileInfo GetPathToConfig(IYamlInclude includeDirective, SupportedServices serviceType)
{
return includeDirective is ConfigYamlInclude;
var include = (ConfigYamlInclude) includeDirective;
if (include.Config is null)
{
throw new YamlIncludeException("`config` property is required.");
}
var configFile = ConvertToAbsolute(include.Config);
if (configFile?.Exists != true)
{
throw new YamlIncludeException($"Include path could not be resolved: {include.Config}");
}
return configFile;
}
private IFileInfo? ConvertToAbsolute(string path)
@ -40,22 +53,4 @@ public class ConfigIncludeProcessor(IFileSystem fs, IAppPaths paths, ILogger log
return null;
}
public IFileInfo GetPathToConfig(IYamlInclude includeDirective, SupportedServices serviceType)
{
var include = (ConfigYamlInclude) includeDirective;
if (include.Config is null)
{
throw new YamlIncludeException("`config` property is required.");
}
var configFile = ConvertToAbsolute(include.Config);
if (configFile?.Exists != true)
{
throw new YamlIncludeException($"Include path could not be resolved: {include.Config}");
}
return configFile;
}
}

@ -5,6 +5,5 @@ namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
public interface IIncludeProcessor
{
bool CanProcess(IYamlInclude includeDirective);
IFileInfo GetPathToConfig(IYamlInclude includeDirective, SupportedServices serviceType);
}

@ -5,5 +5,5 @@ namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
public interface IYamlIncludeResolver
{
IFileInfo GetIncludePath(IYamlInclude includeType, SupportedServices serviceType);
IFileInfo GetIncludePath(IYamlInclude includeDirective, SupportedServices serviceType);
}

@ -64,7 +64,7 @@ public abstract class ServiceConfigMerger<T> where T : ServiceConfigYaml
TrashIds = x.A.TrashIds
.Except(x.B, StringComparer.InvariantCultureIgnoreCase)
.ToList(),
QualityProfiles = x.A.ProfileName is not null
AssignScoresTo = x.A.ProfileName is not null
? new[] {new QualityScoreConfigYaml {Name = x.A.ProfileName, Score = x.A.Score}}
: null
})
@ -75,8 +75,8 @@ public abstract class ServiceConfigMerger<T> where T : ServiceConfigYaml
{
return cfs
.Where(x => x.TrashIds is not null)
.SelectMany(x => x is {QualityProfiles.Count: > 0}
? x.QualityProfiles.Select(y => new FlattenedCfs(y.Name, y.Score, x.TrashIds!))
.SelectMany(x => x is {AssignScoresTo.Count: > 0}
? x.AssignScoresTo.Select(y => new FlattenedCfs(y.Name, y.Score, x.TrashIds!))
: new[] {new FlattenedCfs(null, null, x.TrashIds!)})
.GroupBy(x => (Name: x.ProfileName, x.Score))
.Select(x => new FlattenedCfs(x.Key.Name, x.Key.Score, x.SelectMany(y => y.TrashIds).ToList()))

@ -6,11 +6,6 @@ namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
public class TemplateIncludeProcessor(IConfigTemplateGuideService templates) : IIncludeProcessor
{
public bool CanProcess(IYamlInclude includeDirective)
{
return includeDirective is TemplateYamlInclude;
}
public IFileInfo GetPathToConfig(IYamlInclude includeDirective, SupportedServices serviceType)
{
var include = (TemplateYamlInclude) includeDirective;

@ -1,19 +1,19 @@
using System.IO.Abstractions;
using Autofac.Features.Indexed;
using Recyclarr.TrashGuide;
namespace Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
public class YamlIncludeResolver(IReadOnlyCollection<IIncludeProcessor> includeProcessors) : IYamlIncludeResolver
public class YamlIncludeResolver(IIndex<Type, IIncludeProcessor> processorFactory) : IYamlIncludeResolver
{
public IFileInfo GetIncludePath(IYamlInclude includeType, SupportedServices serviceType)
public IFileInfo GetIncludePath(IYamlInclude includeDirective, SupportedServices serviceType)
{
var processor = includeProcessors.FirstOrDefault(x => x.CanProcess(includeType));
if (processor is null)
if (!processorFactory.TryGetValue(includeDirective.GetType(), out var processor))
{
throw new YamlIncludeException("Include type is not supported");
}
var yamlFile = processor.GetPathToConfig(includeType, serviceType);
var yamlFile = processor.GetPathToConfig(includeDirective, serviceType);
if (!yamlFile.Exists)
{
throw new YamlIncludeException($"Included YAML file does not exist: {yamlFile.FullName}");

@ -0,0 +1,31 @@
namespace Recyclarr.Config.Parsing.PostProcessing.Deprecations;
public class CfQualityProfilesDeprecationCheck(ILogger log) : IConfigDeprecationCheck
{
public bool CheckIfNeeded(ServiceConfigYaml include)
{
return
include.CustomFormats is not null &&
include.CustomFormats.Any(x => x.QualityProfiles is {Count: > 0});
}
public ServiceConfigYaml Transform(ServiceConfigYaml include)
{
log.Warning(
"DEPRECATED: The `quality_profiles` element under `custom_formats` nodes was " +
"detected in your config. This has been renamed to `assign_scores_to`. " +
"See: https://recyclarr.dev/wiki/upgrade-guide/v8.0/#assign-scores-to");
// CustomFormats is checked for null in the CheckIfNeeded() method, which is called first.
var cfs = include.CustomFormats!.Select(x => x with
{
AssignScoresTo = [..x.AssignScoresTo ?? [], ..x.QualityProfiles ?? []],
QualityProfiles = null
});
return include with
{
CustomFormats = cfs.ToList()
};
}
}

@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
namespace Recyclarr.Config.Parsing.PostProcessing.Deprecations;
public class ConfigDeprecations(IOrderedEnumerable<IConfigDeprecationCheck> deprecationChecks)
{
[SuppressMessage("SonarLint", "S3267: Loops should be simplified with LINQ expressions", Justification =
"The 'Where' condition must happen after each Transform() call instead of all at once")]
public T CheckAndTransform<T>(T include) where T : ServiceConfigYaml
{
foreach (var check in deprecationChecks)
{
if (check.CheckIfNeeded(include))
{
include = (T) check.Transform(include);
}
}
return include;
}
}

@ -0,0 +1,7 @@
namespace Recyclarr.Config.Parsing.PostProcessing.Deprecations;
public interface IConfigDeprecationCheck
{
ServiceConfigYaml Transform(ServiceConfigYaml include);
bool CheckIfNeeded(ServiceConfigYaml include);
}

@ -1,24 +1,42 @@
using Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
using Recyclarr.Config.Parsing.PostProcessing.Deprecations;
using Recyclarr.Platform;
using Recyclarr.TrashGuide;
using Serilog.Context;
namespace Recyclarr.Config.Parsing.PostProcessing;
public class IncludePostProcessor(
public sealed class IncludePostProcessor(
ILogger log,
ConfigParser parser,
ConfigValidationExecutor validator,
IYamlIncludeResolver includeResolver)
: IConfigPostProcessor
IYamlIncludeResolver includeResolver,
ConfigDeprecations deprecations)
: IConfigPostProcessor, IDisposable
{
private IDisposable? _logScope;
public void Dispose()
{
_logScope?.Dispose();
}
public RootConfigYaml Process(RootConfigYaml config)
{
return new RootConfigYaml
try
{
Radarr = ProcessIncludes(config.Radarr, new RadarrConfigMerger(), SupportedServices.Radarr),
Sonarr = ProcessIncludes(config.Sonarr, new SonarrConfigMerger(), SupportedServices.Sonarr)
};
config = config with
{
Radarr = ProcessIncludes(config.Radarr, new RadarrConfigMerger(), SupportedServices.Radarr),
Sonarr = ProcessIncludes(config.Sonarr, new SonarrConfigMerger(), SupportedServices.Sonarr)
};
}
finally
{
_logScope?.Dispose();
}
return config;
}
private Dictionary<string, T>? ProcessIncludes<T>(
@ -44,7 +62,11 @@ public class IncludePostProcessor(
// Combine all includes together first
var aggregateInclude = config.Include
.Select(x => LoadYamlInclude<T>(x, serviceType))
.Select(x =>
{
var include = LoadYamlInclude<T>(x, serviceType);
return deprecations.CheckAndTransform(include);
})
.Aggregate(new T(), merger.Merge);
// Merge the config into the aggregated includes so that root config values overwrite included values.
@ -61,11 +83,11 @@ public class IncludePostProcessor(
return mergedConfigs;
}
private T LoadYamlInclude<T>(IYamlInclude includeType, SupportedServices serviceType)
private T LoadYamlInclude<T>(IYamlInclude includeDirective, SupportedServices serviceType)
where T : ServiceConfigYaml
{
var yamlFile = includeResolver.GetIncludePath(includeType, serviceType);
using var logScope = LogContext.PushProperty(LogProperty.Scope, $"Include {yamlFile.Name}");
var yamlFile = includeResolver.GetIncludePath(includeDirective, serviceType);
_logScope = LogContext.PushProperty(LogProperty.Scope, $"Include {yamlFile.Name}");
var configToMerge = parser.Load<T>(yamlFile);
if (configToMerge is null)

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Extras.Ordering" />
<PackageReference Include="AutoMapper" />
<PackageReference Include="AutoMapper.Contrib.Autofac.DependencyInjection" />
<PackageReference Include="FluentValidation" />

@ -33,7 +33,7 @@ public class QualityProfileConfigPhaseTest
fixture.Inject<IServiceConfiguration>(SetupCfs(new CustomFormatConfig
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new()
{
@ -70,7 +70,7 @@ public class QualityProfileConfigPhaseTest
fixture.Inject<IServiceConfiguration>(SetupCfs(new CustomFormatConfig
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new()
{
@ -105,7 +105,7 @@ public class QualityProfileConfigPhaseTest
fixture.Inject<IServiceConfiguration>(SetupCfs(new CustomFormatConfig
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new()
{
@ -144,7 +144,7 @@ public class QualityProfileConfigPhaseTest
new CustomFormatConfig
{
TrashIds = new[] {"id1"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new() {Name = "test_profile1", Score = 100}
}
@ -152,7 +152,7 @@ public class QualityProfileConfigPhaseTest
new CustomFormatConfig
{
TrashIds = new[] {"id1"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new() {Name = "test_profile1", Score = 200}
}
@ -160,7 +160,7 @@ public class QualityProfileConfigPhaseTest
new CustomFormatConfig
{
TrashIds = new[] {"id1"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new() {Name = "test_profile2", Score = 200}
}
@ -168,7 +168,7 @@ public class QualityProfileConfigPhaseTest
new CustomFormatConfig
{
TrashIds = new[] {"id1"},
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new() {Name = "test_profile2", Score = 100}
}
@ -206,9 +206,9 @@ public class QualityProfileConfigPhaseTest
new CustomFormatConfig
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityProfileScoreConfig {Name = "test_profile"}
new AssignScoresToConfig {Name = "test_profile"}
}
}
},
@ -245,7 +245,7 @@ public class QualityProfileConfigPhaseTest
fixture.Inject<IServiceConfiguration>(SetupCfs(new CustomFormatConfig
{
TrashIds = Array.Empty<string>(),
QualityProfiles = new List<QualityProfileScoreConfig>
AssignScoresTo = new List<AssignScoresToConfig>
{
new()
{
@ -277,7 +277,7 @@ public class QualityProfileConfigPhaseTest
fixture.Inject<IServiceConfiguration>(SetupCfs(new CustomFormatConfig
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = Array.Empty<QualityProfileScoreConfig>()
AssignScoresTo = Array.Empty<AssignScoresToConfig>()
}));
var context = new QualityProfilePipelineContext();

@ -6,17 +6,33 @@ namespace Recyclarr.TestLibrary.Autofac;
public class StubAutofacIndex<TKey, TValue> : IIndex<TKey, TValue>
where TKey : notnull
{
private readonly Dictionary<TKey, TValue> _values = new();
private Dictionary<TKey, TValue> _values = new();
public StubAutofacIndex()
{
}
public StubAutofacIndex(Dictionary<TKey, TValue> values)
{
_values = values;
}
public void Add(TKey key, TValue value)
{
_values.Add(key, value);
}
public void AddRange(IEnumerable<(TKey, TValue)> pairs)
{
_values = _values.Union(pairs.ToDictionary(x => x.Item1, x => x.Item2)).ToDictionary();
}
public bool TryGetValue(TKey key, [UnscopedRef] out TValue value)
{
return _values.TryGetValue(key, out value!);
}
public TValue this[TKey key] => _values[key];
public IReadOnlyCollection<TKey> Keys => _values.Keys;
public IReadOnlyCollection<TValue> Values => _values.Values;
}

@ -7,14 +7,6 @@ namespace Recyclarr.Tests.Config.Parsing.PostProcessing.ConfigMerging;
[TestFixture]
public class ConfigIncludeProcessorTest
{
[Test, AutoMockData]
public void Can_process_expected_type(
ConfigIncludeProcessor sut)
{
var result = sut.CanProcess(new ConfigYamlInclude());
result.Should().BeTrue();
}
[Test, AutoMockData]
public void Throw_when_null_include_path(
ConfigIncludeProcessor sut)

@ -16,7 +16,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100}
}
@ -45,7 +45,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100}
}
@ -70,7 +70,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100},
new QualityScoreConfigYaml {Name = "d", Score = 101},
@ -80,7 +80,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "f", Score = 100}
}
@ -95,7 +95,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id3", "id4"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "d", Score = 200}
}
@ -103,7 +103,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id5", "id6"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "e", Score = 300}
}
@ -111,7 +111,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 50}
}
@ -130,7 +130,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 100}
}
@ -138,7 +138,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "d", Score = 101}
}
@ -146,7 +146,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1", "id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "e", Score = 102}
}
@ -154,7 +154,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id2"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "f", Score = 100}
}
@ -162,7 +162,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id3", "id4"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "d", Score = 200}
}
@ -170,7 +170,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id5", "id6"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "e", Score = 300}
}
@ -178,7 +178,7 @@ public class MergeCustomFormatsTest
new CustomFormatConfigYaml
{
TrashIds = new[] {"id1"},
QualityProfiles = new[]
AssignScoresTo = new[]
{
new QualityScoreConfigYaml {Name = "c", Score = 50}
}

@ -7,14 +7,6 @@ namespace Recyclarr.Tests.Config.Parsing.PostProcessing.ConfigMerging;
[TestFixture]
public class TemplateIncludeProcessorTest
{
[Test, AutoMockData]
public void Can_process_expected_type(
TemplateIncludeProcessor sut)
{
var result = sut.CanProcess(new TemplateYamlInclude());
result.Should().BeTrue();
}
[Test, AutoMockData]
public void Obtain_path_from_template(
[Frozen(Matching.ImplementedInterfaces)] MockFileSystem fs,

@ -1,23 +1,35 @@
using System.Diagnostics.CodeAnalysis;
using System.IO.Abstractions;
using Recyclarr.Config.Parsing.PostProcessing.ConfigMerging;
using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashGuide;
namespace Recyclarr.Tests.Config.Parsing.PostProcessing.ConfigMerging;
[TestFixture]
[SuppressMessage("Design", "CA1034:Nested types should not be visible")]
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public class YamlIncludeResolverTest
{
[Test]
public void Find_and_return_processor()
public abstract class TestYamlInclude1 : IYamlInclude;
public abstract class TestYamlInclude2 : IYamlInclude;
public abstract class TestYamlInclude3 : IYamlInclude;
[Test, AutoMockData]
public void Find_and_return_processor(
[Frozen(Matching.ImplementedInterfaces)] StubAutofacIndex<Type, IIncludeProcessor> index,
YamlIncludeResolver sut)
{
var processors = new[]
var processors = new List<(IYamlInclude Directive, IIncludeProcessor Value)>
{
Substitute.For<IIncludeProcessor>(),
Substitute.For<IIncludeProcessor>()
(Substitute.ForPartsOf<TestYamlInclude1>(), Substitute.For<IIncludeProcessor>()),
(Substitute.ForPartsOf<TestYamlInclude2>(), Substitute.For<IIncludeProcessor>())
};
processors[1].CanProcess(default!).ReturnsForAnyArgs(true);
processors[1].GetPathToConfig(default!, default!).ReturnsForAnyArgs(_ =>
index.AddRange(processors.Select(x => (x.Directive.GetType(), x.Value)));
processors[1].Value.GetPathToConfig(default!, default!).ReturnsForAnyArgs(_ =>
{
var fileInfo = Substitute.For<IFileInfo>();
fileInfo.Exists.Returns(true);
@ -25,46 +37,50 @@ public class YamlIncludeResolverTest
return fileInfo;
});
var sut = new YamlIncludeResolver(processors);
var result = sut.GetIncludePath(Substitute.For<IYamlInclude>(), SupportedServices.Radarr);
var result = sut.GetIncludePath(processors[1].Directive, SupportedServices.Radarr);
result.FullName.Should().Be("the_path");
}
[Test]
public void Throw_when_no_matching_processor()
[Test, AutoMockData]
public void Throw_when_no_matching_processor(
[Frozen(Matching.ImplementedInterfaces)] StubAutofacIndex<Type, IIncludeProcessor> index,
YamlIncludeResolver sut)
{
var processors = new[]
var processors = new List<(IYamlInclude Directive, IIncludeProcessor Value)>
{
Substitute.For<IIncludeProcessor>(),
Substitute.For<IIncludeProcessor>()
(Substitute.ForPartsOf<TestYamlInclude1>(), Substitute.For<IIncludeProcessor>()),
(Substitute.ForPartsOf<TestYamlInclude2>(), Substitute.For<IIncludeProcessor>())
};
var sut = new YamlIncludeResolver(processors);
var act = () => sut.GetIncludePath(Substitute.For<IYamlInclude>(), SupportedServices.Radarr);
index.AddRange(processors.Select(x => (x.Directive.GetType(), x.Value)));
var act = () => sut.GetIncludePath(Substitute.ForPartsOf<TestYamlInclude3>(), SupportedServices.Radarr);
act.Should().Throw<YamlIncludeException>().WithMessage("*type is not supported*");
}
[Test]
public void Throw_when_path_does_not_exist()
[Test, AutoMockData]
public void Throw_when_path_does_not_exist(
[Frozen(Matching.ImplementedInterfaces)] StubAutofacIndex<Type, IIncludeProcessor> index,
YamlIncludeResolver sut)
{
var processors = new[]
var processors = new List<(IYamlInclude Directive, IIncludeProcessor Value)>
{
Substitute.For<IIncludeProcessor>(),
Substitute.For<IIncludeProcessor>()
(Substitute.ForPartsOf<TestYamlInclude1>(), Substitute.For<IIncludeProcessor>()),
(Substitute.ForPartsOf<TestYamlInclude2>(), Substitute.For<IIncludeProcessor>())
};
processors[1].CanProcess(default!).ReturnsForAnyArgs(true);
processors[1].GetPathToConfig(default!, default!).ReturnsForAnyArgs(_ =>
index.AddRange(processors.Select(x => (x.Directive.GetType(), x.Value)));
processors[1].Value.GetPathToConfig(default!, default!).ReturnsForAnyArgs(_ =>
{
var fileInfo = Substitute.For<IFileInfo>();
fileInfo.Exists.Returns(false);
return fileInfo;
});
var sut = new YamlIncludeResolver(processors);
var act = () => sut.GetIncludePath(Substitute.For<IYamlInclude>(), SupportedServices.Radarr);
var act = () => sut.GetIncludePath(processors[1].Directive, SupportedServices.Radarr);
act.Should().Throw<YamlIncludeException>().WithMessage("*does not exist*");
}

@ -19,7 +19,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new List<string> {"01234567890123456789012345678901"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -52,7 +52,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new[] {"valid"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -85,7 +85,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new[] {"valid"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -119,7 +119,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new[] {"valid"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -155,7 +155,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new List<string> {"valid"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -188,7 +188,7 @@ public class YamlConfigValidatorTest
new()
{
TrashIds = new List<string> {"valid"},
QualityProfiles = new List<QualityScoreConfigYaml>
AssignScoresTo = new List<QualityScoreConfigYaml>
{
new()
{
@ -207,7 +207,7 @@ public class YamlConfigValidatorTest
var result = validator.TestValidate(config);
result.ShouldHaveValidationErrorFor(FirstCf +
$"{nameof(CustomFormatConfig.QualityProfiles)}[0].{nameof(QualityProfileScoreConfig.Name)}");
$"{nameof(CustomFormatConfig.AssignScoresTo)}[0].{nameof(AssignScoresToConfig.Name)}");
}
[Test]

Loading…
Cancel
Save