From b14787e4712bbc82b5de5623c445bc029f1cc7a0 Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sat, 28 Oct 2023 22:17:43 -0500 Subject: [PATCH] refactor: Make quality profile pipeline generic --- src/Recyclarr.Cli/CompositionRoot.cs | 2 +- .../Pipelines/Generic/GenericSyncPipeline.cs | 2 + .../Pipelines/Generic/ILogPipelinePhase.cs | 1 + .../PipelinePhases/MediaNamingLogPhase.cs | 4 + .../Models/ProcessedQualityProfileData.cs | 14 + .../Models/QualityProfileTransactionData.cs | 15 + .../Models/QualityProfileUpdateReason.cs | 7 + .../QualityProfileApiFetchPhase.cs | 6 +- .../QualityProfileApiPersistencePhase.cs | 66 +--- .../QualityProfileConfigPhase.cs | 18 +- .../PipelinePhases/QualityProfileLogPhase.cs | 123 +++++++ .../QualityProfileNoticePhase.cs | 61 ---- .../QualityProfilePreviewPhase.cs | 7 +- .../QualityProfileTransactionPhase.cs | 67 ++-- .../QualityProfileAutofacModule.cs | 16 +- .../QualityProfilePipelineContext.cs | 23 ++ .../QualityProfileStatCalculator.cs | 2 +- .../QualityProfileSyncPipeline.cs | 41 --- .../QualityProfile/UpdatedFormatScore.cs | 1 + .../QualityProfile/UpdatedQualityProfile.cs | 1 + .../UpdatedQualityProfileValidator.cs | 1 + .../PipelinePhases/QualitySizeLogPhase.cs | 4 + .../PipelinePhases/ReleaseProfileLogPhase.cs | 4 + .../Tags/PipelinePhases/TagLogPhase.cs | 4 + tests/Recyclarr.Cli.Tests/NewQp.cs | 1 + .../QualityProfileConfigPhaseTest.cs | 36 ++- .../QualityProfileTransactionPhaseTest.cs | 306 ++++++++++-------- .../UpdatedQualityProfileTest.cs | 1 + .../UpdatedQualityProfileValidatorTest.cs | 1 + 29 files changed, 458 insertions(+), 377 deletions(-) create mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/Models/ProcessedQualityProfileData.cs create mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileTransactionData.cs create mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileUpdateReason.cs create mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileLogPhase.cs delete mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileNoticePhase.cs create mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfilePipelineContext.cs rename src/Recyclarr.Cli/Pipelines/QualityProfile/{PipelinePhases => }/QualityProfileStatCalculator.cs (97%) delete mode 100644 src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileSyncPipeline.cs diff --git a/src/Recyclarr.Cli/CompositionRoot.cs b/src/Recyclarr.Cli/CompositionRoot.cs index 3921e6da..81c33020 100644 --- a/src/Recyclarr.Cli/CompositionRoot.cs +++ b/src/Recyclarr.Cli/CompositionRoot.cs @@ -82,7 +82,7 @@ public static class CompositionRoot // There are indirect dependencies between pipelines. typeof(GenericSyncPipeline), typeof(CustomFormatSyncPipeline), - typeof(QualityProfileSyncPipeline), + typeof(GenericSyncPipeline), typeof(GenericSyncPipeline), typeof(GenericSyncPipeline), typeof(GenericSyncPipeline)) diff --git a/src/Recyclarr.Cli/Pipelines/Generic/GenericSyncPipeline.cs b/src/Recyclarr.Cli/Pipelines/Generic/GenericSyncPipeline.cs index 1514da6e..3adeb51b 100644 --- a/src/Recyclarr.Cli/Pipelines/Generic/GenericSyncPipeline.cs +++ b/src/Recyclarr.Cli/Pipelines/Generic/GenericSyncPipeline.cs @@ -24,6 +24,8 @@ public class GenericSyncPipeline(ILogger log, GenericPipelinePhases where TContext : IPipelineContext { bool LogConfigPhaseAndExitIfNeeded(TContext context); + void LogTransactionNotices(TContext context); void LogPersistenceResults(TContext context); } diff --git a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingLogPhase.cs b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingLogPhase.cs index 624c8929..1c102f34 100644 --- a/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingLogPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/MediaNaming/PipelinePhases/MediaNamingLogPhase.cs @@ -36,6 +36,10 @@ public class MediaNamingLogPhase(ILogger log) : ILogPipelinePhase CfScores { get; init; } = new List(); + public IList ScorelessCfs { get; } = new List(); +} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileTransactionData.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileTransactionData.cs new file mode 100644 index 00000000..8b95bcdc --- /dev/null +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileTransactionData.cs @@ -0,0 +1,15 @@ +using System.Diagnostics.CodeAnalysis; +using FluentValidation.Results; + +namespace Recyclarr.Cli.Pipelines.QualityProfile.Models; + +public record InvalidProfileData(UpdatedQualityProfile Profile, IReadOnlyCollection Errors); + +[SuppressMessage("Usage", "CA2227:Collection properties should be read only")] +public record QualityProfileTransactionData +{ + public ICollection NonExistentProfiles { get; init; } = new List(); + public ICollection InvalidProfiles { get; init; } = new List(); + public ICollection UnchangedProfiles { get; set; } = new List(); + public ICollection ChangedProfiles { get; set; } = new List(); +} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileUpdateReason.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileUpdateReason.cs new file mode 100644 index 00000000..70847757 --- /dev/null +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/Models/QualityProfileUpdateReason.cs @@ -0,0 +1,7 @@ +namespace Recyclarr.Cli.Pipelines.QualityProfile.Models; + +public enum QualityProfileUpdateReason +{ + New, + Changed +} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiFetchPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiFetchPhase.cs index 1eb64e90..7a43f03e 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiFetchPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiFetchPhase.cs @@ -1,3 +1,4 @@ +using Recyclarr.Cli.Pipelines.Generic; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile; @@ -6,11 +7,12 @@ namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; public record QualityProfileServiceData(IReadOnlyList Profiles, QualityProfileDto Schema); public class QualityProfileApiFetchPhase(IQualityProfileApiService api) + : IApiFetchPipelinePhase { - public async Task Execute(IServiceConfiguration config) + public async Task Execute(QualityProfilePipelineContext context, IServiceConfiguration config) { var profiles = await api.GetQualityProfiles(config); var schema = await api.GetSchema(config); - return new QualityProfileServiceData(profiles.AsReadOnly(), schema); + context.ApiFetchOutput = new QualityProfileServiceData(profiles.AsReadOnly(), schema); } } diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs index ff6184b6..159c516c 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileApiPersistencePhase.cs @@ -1,29 +1,16 @@ +using Recyclarr.Cli.Pipelines.Generic; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile; namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; -public class QualityProfileApiPersistencePhase( - ILogger log, - IQualityProfileApiService api, - QualityProfileStatCalculator statCalculator) +public class QualityProfileApiPersistencePhase(IQualityProfileApiService api) + : IApiPersistencePipelinePhase { - public async Task Execute(IServiceConfiguration config, QualityProfileTransactionData transactions) + public async Task Execute(QualityProfilePipelineContext context, IServiceConfiguration config) { - var profilesWithStats = transactions.UpdatedProfiles - .Select(x => statCalculator.Calculate(x)) - .ToLookup(x => x.HasChanges); - - // Profiles without changes (false) get logged - var unchangedProfiles = profilesWithStats[false].ToList(); - if (unchangedProfiles.Count != 0) - { - log.Debug("These profiles have no changes and will not be persisted: {Profiles}", - unchangedProfiles.Select(x => x.Profile.ProfileName)); - } - - // Profiles with changes (true) get sent to the service - var changedProfiles = profilesWithStats[true].ToList(); + var changedProfiles = context.TransactionOutput.ChangedProfiles; foreach (var profile in changedProfiles.Select(x => x.Profile)) { var dto = profile.BuildUpdatedDto(); @@ -42,46 +29,5 @@ public class QualityProfileApiPersistencePhase( throw new InvalidOperationException($"Unsupported UpdateReason: {profile.UpdateReason}"); } } - - LogUpdates(changedProfiles); - } - - private void LogUpdates(IReadOnlyCollection changedProfiles) - { - var createdProfiles = changedProfiles - .Where(x => x.Profile.UpdateReason == QualityProfileUpdateReason.New) - .Select(x => x.Profile.ProfileName) - .ToList(); - - if (createdProfiles.Count > 0) - { - log.Information("Created {Count} Profiles: {Names}", createdProfiles.Count, createdProfiles); - } - - var updatedProfiles = changedProfiles - .Where(x => x.Profile.UpdateReason == QualityProfileUpdateReason.Changed) - .Select(x => x.Profile.ProfileName) - .ToList(); - - if (updatedProfiles.Count > 0) - { - log.Information("Updated {Count} Profiles: {Names}", updatedProfiles.Count, updatedProfiles); - } - - if (changedProfiles.Count != 0) - { - var numProfiles = changedProfiles.Count; - var numQuality = changedProfiles.Count(x => x.QualitiesChanged); - var numScores = changedProfiles.Count(x => x.ScoresChanged); - - log.Information( - "A total of {NumProfiles} profiles were synced. {NumQuality} contain quality changes and " + - "{NumScores} contain updated scores", - numProfiles, numQuality, numScores); - } - else - { - log.Information("All quality profiles are up to date!"); - } } } diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhase.cs index 0086f6dd..e180d5a8 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhase.cs @@ -1,23 +1,16 @@ using Recyclarr.Cli.Pipelines.CustomFormat.Models; +using Recyclarr.Cli.Pipelines.Generic; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Common.Extensions; using Recyclarr.Config.Models; using Recyclarr.TrashGuide.CustomFormat; namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; -public record ProcessedQualityProfileScore(string TrashId, string CfName, int FormatId, int Score); - -public record ProcessedQualityProfileData -{ - public required QualityProfileConfig Profile { get; init; } - public bool ShouldCreate { get; init; } = true; - public IList CfScores { get; init; } = new List(); - public IList ScorelessCfs { get; } = new List(); -} - public class QualityProfileConfigPhase(ILogger log, ProcessedCustomFormatCache cache) + : IConfigPipelinePhase { - public IReadOnlyCollection Execute(IServiceConfiguration config) + public Task Execute(QualityProfilePipelineContext context, IServiceConfiguration config) { // 1. For each group of CFs that has a quality profile specified // 2. For each quality profile score config in that CF group @@ -57,7 +50,8 @@ public class QualityProfileConfigPhase(ILogger log, ProcessedCustomFormatCache c var profilesToReturn = allProfiles.Values.ToList(); PrintDiagnostics(profilesToReturn); - return profilesToReturn; + context.ConfigOutput = profilesToReturn; + return Task.CompletedTask; } private void PrintDiagnostics(IEnumerable profiles) diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileLogPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileLogPhase.cs new file mode 100644 index 00000000..9a09d86c --- /dev/null +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileLogPhase.cs @@ -0,0 +1,123 @@ +using Recyclarr.Cli.Pipelines.Generic; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; +using Recyclarr.Common.FluentValidation; + +namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; + +public class QualityProfileLogPhase(ILogger log) : ILogPipelinePhase +{ + public bool LogConfigPhaseAndExitIfNeeded(QualityProfilePipelineContext context) + { + if (!context.ConfigOutput.Any()) + { + log.Debug("No Quality Profiles to process"); + return true; + } + + return false; + } + + public void LogTransactionNotices(QualityProfilePipelineContext context) + { + var transactions = context.TransactionOutput; + + if (transactions.NonExistentProfiles.Count > 0) + { + log.Warning( + "The following quality profile names have no definition in the top-level `quality_profiles` " + + "list *and* do not exist in the remote service. Either create them manually in the service *or* add " + + "them to the top-level `quality_profiles` section so that Recyclarr can create the profiles for you"); + log.Warning("{QualityProfileNames}", transactions.NonExistentProfiles); + } + + if (transactions.InvalidProfiles.Count > 0) + { + log.Warning( + "The following validation errors occurred for one or more quality profiles. " + + "These profiles will *not* be synced"); + + var numErrors = 0; + + foreach (var (profile, errors) in transactions.InvalidProfiles) + { + numErrors += errors.LogValidationErrors(log, $"Profile '{profile.ProfileName}'"); + } + + if (numErrors > 0) + { + log.Error("Profile validation failed with {Count} errors", numErrors); + } + } + + var invalidQualityNames = transactions.ChangedProfiles + .Select(x => (x.Profile.ProfileName, x.Profile.UpdatedQualities.InvalidQualityNames)) + .Where(x => x.InvalidQualityNames.Count != 0) + .ToList(); + + foreach (var (profileName, invalidNames) in invalidQualityNames) + { + log.Warning("Quality profile '{ProfileName}' references invalid quality names: {InvalidNames}", + profileName, invalidNames); + } + + var invalidCfExceptNames = transactions.ChangedProfiles + .Where(x => x.Profile.InvalidExceptCfNames.Count != 0) + .Select(x => (x.Profile.ProfileName, x.Profile.InvalidExceptCfNames)); + + foreach (var (profileName, invalidNames) in invalidCfExceptNames) + { + log.Warning( + "`except` under `reset_unmatched_scores` in quality profile '{ProfileName}' has invalid " + + "CF names: {CfNames}", profileName, invalidNames); + } + } + + public void LogPersistenceResults(QualityProfilePipelineContext context) + { + var changedProfiles = context.TransactionOutput.ChangedProfiles; + + // Profiles without changes get logged + var unchangedProfiles = context.TransactionOutput.UnchangedProfiles; + if (unchangedProfiles.Count != 0) + { + log.Debug("These profiles have no changes and will not be persisted: {Profiles}", + unchangedProfiles.Select(x => x.Profile.ProfileName)); + } + + var createdProfiles = changedProfiles + .Where(x => x.Profile.UpdateReason == QualityProfileUpdateReason.New) + .Select(x => x.Profile.ProfileName) + .ToList(); + + if (createdProfiles.Count > 0) + { + log.Information("Created {Count} Profiles: {Names}", createdProfiles.Count, createdProfiles); + } + + var updatedProfiles = changedProfiles + .Where(x => x.Profile.UpdateReason == QualityProfileUpdateReason.Changed) + .Select(x => x.Profile.ProfileName) + .ToList(); + + if (updatedProfiles.Count > 0) + { + log.Information("Updated {Count} Profiles: {Names}", updatedProfiles.Count, updatedProfiles); + } + + if (changedProfiles.Count != 0) + { + var numProfiles = changedProfiles.Count; + var numQuality = changedProfiles.Count(x => x.QualitiesChanged); + var numScores = changedProfiles.Count(x => x.ScoresChanged); + + log.Information( + "A total of {NumProfiles} profiles were synced. {NumQuality} contain quality changes and " + + "{NumScores} contain updated scores", + numProfiles, numQuality, numScores); + } + else + { + log.Information("All quality profiles are up to date!"); + } + } +} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileNoticePhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileNoticePhase.cs deleted file mode 100644 index 8c8a092d..00000000 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileNoticePhase.cs +++ /dev/null @@ -1,61 +0,0 @@ -using JetBrains.Annotations; -using Recyclarr.Common.FluentValidation; - -namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; - -[UsedImplicitly] -public class QualityProfileNoticePhase(ILogger log) -{ - public void Execute(QualityProfileTransactionData transactions) - { - if (transactions.NonExistentProfiles.Count > 0) - { - log.Warning( - "The following quality profile names have no definition in the top-level `quality_profiles` " + - "list *and* do not exist in the remote service. Either create them manually in the service *or* add " + - "them to the top-level `quality_profiles` section so that Recyclarr can create the profiles for you"); - log.Warning("{QualityProfileNames}", transactions.NonExistentProfiles); - } - - if (transactions.InvalidProfiles.Count > 0) - { - log.Warning( - "The following validation errors occurred for one or more quality profiles. " + - "These profiles will *not* be synced"); - - var numErrors = 0; - - foreach (var (profile, errors) in transactions.InvalidProfiles) - { - numErrors += errors.LogValidationErrors(log, $"Profile '{profile.ProfileName}'"); - } - - if (numErrors > 0) - { - log.Error("Profile validation failed with {Count} errors", numErrors); - } - } - - var invalidQualityNames = transactions.UpdatedProfiles - .Select(x => (x.ProfileName, x.UpdatedQualities.InvalidQualityNames)) - .Where(x => x.InvalidQualityNames.Count != 0) - .ToList(); - - foreach (var (profileName, invalidNames) in invalidQualityNames) - { - log.Warning("Quality profile '{ProfileName}' references invalid quality names: {InvalidNames}", - profileName, invalidNames); - } - - var invalidCfExceptNames = transactions.UpdatedProfiles - .Where(x => x.InvalidExceptCfNames.Count != 0) - .Select(x => (x.ProfileName, x.InvalidExceptCfNames)); - - foreach (var (profileName, invalidNames) in invalidCfExceptNames) - { - log.Warning( - "`except` under `reset_unmatched_scores` in quality profile '{ProfileName}' has invalid " + - "CF names: {CfNames}", profileName, invalidNames); - } - } -} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs index 2890a350..e9e2871c 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfilePreviewPhase.cs @@ -1,16 +1,17 @@ +using Recyclarr.Cli.Pipelines.Generic; using Recyclarr.ServarrApi.QualityProfile; using Spectre.Console; using Spectre.Console.Rendering; namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; -public class QualityProfilePreviewPhase(IAnsiConsole console) +public class QualityProfilePreviewPhase(IAnsiConsole console) : IPreviewPipelinePhase { - public void Execute(QualityProfileTransactionData transactions) + public void Execute(QualityProfilePipelineContext context) { var tree = new Tree("Quality Profile Changes [red](Preview)[/]"); - foreach (var profile in transactions.UpdatedProfiles) + foreach (var profile in context.TransactionOutput.ChangedProfiles.Select(x => x.Profile)) { var profileTree = new Tree(Markup.FromInterpolated( $"[yellow]{profile.ProfileName}[/] (Change Reason: [green]{profile.UpdateReason}[/])")); diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs index eb4dfe58..3104e850 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhase.cs @@ -1,5 +1,5 @@ -using System.Diagnostics.CodeAnalysis; -using FluentValidation.Results; +using Recyclarr.Cli.Pipelines.Generic; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Common.Extensions; using Recyclarr.Common.FluentValidation; using Recyclarr.Config.Models; @@ -7,52 +7,47 @@ using Recyclarr.ServarrApi.QualityProfile; namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; -public enum QualityProfileUpdateReason +public class QualityProfileTransactionPhase(QualityProfileStatCalculator statCalculator) + : ITransactionPipelinePhase { - New, - Changed -} - -public record InvalidProfileData(UpdatedQualityProfile Profile, IReadOnlyCollection Errors); - -public record QualityProfileTransactionData -{ - [SuppressMessage("Usage", "CA2227:Collection properties should be read only")] - public ICollection UpdatedProfiles { get; set; } = new List(); - public ICollection NonExistentProfiles { get; init; } = new List(); - public ICollection InvalidProfiles { get; init; } = new List(); -} - -public class QualityProfileTransactionPhase -{ - [SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")] - [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = - "This non-static method establishes a pattern that will eventually become an interface")] - public QualityProfileTransactionData Execute( - IReadOnlyCollection guideData, - QualityProfileServiceData serviceData) + public void Execute(QualityProfilePipelineContext context) { var transactions = new QualityProfileTransactionData(); - BuildUpdatedProfiles(transactions, guideData, serviceData); - UpdateProfileScores(transactions.UpdatedProfiles); + var updatedProfiles = BuildUpdatedProfiles(transactions, context.ConfigOutput, context.ApiFetchOutput); + UpdateProfileScores(updatedProfiles); - ValidateProfiles(transactions); + updatedProfiles = ValidateProfiles(updatedProfiles, transactions.InvalidProfiles); - return transactions; + AssignProfiles(transactions, updatedProfiles); + context.TransactionOutput = transactions; } - private static void ValidateProfiles(QualityProfileTransactionData transactions) + private void AssignProfiles( + QualityProfileTransactionData transactions, + IEnumerable updatedProfiles) + { + var profilesWithStats = updatedProfiles + .Select(statCalculator.Calculate) + .ToLookup(x => x.HasChanges); + + transactions.UnchangedProfiles = profilesWithStats[false].ToList(); + transactions.ChangedProfiles = profilesWithStats[true].ToList(); + } + + private static List ValidateProfiles( + IEnumerable transactions, + ICollection invalidProfiles) { var validator = new UpdatedQualityProfileValidator(); - transactions.UpdatedProfiles = transactions.UpdatedProfiles + return transactions .IsValid(validator, (errors, profile) => - transactions.InvalidProfiles.Add(new InvalidProfileData(profile, errors))) + invalidProfiles.Add(new InvalidProfileData(profile, errors))) .ToList(); } - private static void BuildUpdatedProfiles( + private static List BuildUpdatedProfiles( QualityProfileTransactionData transactions, IEnumerable guideData, QualityProfileServiceData serviceData) @@ -68,6 +63,8 @@ public class QualityProfileTransactionPhase (x, y) => (x, y.FirstOrDefault()), StringComparer.InvariantCultureIgnoreCase); + var updatedProfiles = new List(); + foreach (var (config, dto) in matchedProfiles) { if (dto is null && !config.ShouldCreate) @@ -79,7 +76,7 @@ public class QualityProfileTransactionPhase var organizer = new QualityItemOrganizer(); var newDto = dto ?? serviceData.Schema; - transactions.UpdatedProfiles.Add(new UpdatedQualityProfile + updatedProfiles.Add(new UpdatedQualityProfile { ProfileConfig = config, ProfileDto = newDto, @@ -87,6 +84,8 @@ public class QualityProfileTransactionPhase UpdatedQualities = organizer.OrganizeItems(newDto, config.Profile) }); } + + return updatedProfiles; } private static void UpdateProfileScores(IEnumerable updatedProfiles) diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileAutofacModule.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileAutofacModule.cs index 2c6d5a74..63261213 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileAutofacModule.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileAutofacModule.cs @@ -1,5 +1,4 @@ using Autofac; -using Autofac.Extras.AggregateService; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; namespace Recyclarr.Cli.Pipelines.QualityProfile; @@ -12,12 +11,13 @@ public class QualityProfileAutofacModule : Module builder.RegisterType(); - builder.RegisterAggregateService(); - builder.RegisterType(); - builder.RegisterType(); - builder.RegisterType(); - builder.RegisterType(); - builder.RegisterType(); - builder.RegisterType(); + builder.RegisterTypes( + typeof(QualityProfileConfigPhase), + typeof(QualityProfilePreviewPhase), + typeof(QualityProfileApiFetchPhase), + typeof(QualityProfileTransactionPhase), + typeof(QualityProfileApiPersistencePhase), + typeof(QualityProfileLogPhase)) + .AsImplementedInterfaces(); } } diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfilePipelineContext.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfilePipelineContext.cs new file mode 100644 index 00000000..e2cbdfb4 --- /dev/null +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfilePipelineContext.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.CodeAnalysis; +using Recyclarr.Cli.Pipelines.Generic; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; +using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; +using Recyclarr.Common; + +namespace Recyclarr.Cli.Pipelines.QualityProfile; + +[SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = + "Context objects are similar to DTOs; for usability we want to assign not append")] +public class QualityProfilePipelineContext : IPipelineContext +{ + public string PipelineDescription => "Quality Definition Pipeline"; + public IReadOnlyCollection SupportedServiceTypes { get; } = new[] + { + SupportedServices.Sonarr, + SupportedServices.Radarr + }; + + public IList ConfigOutput { get; set; } = default!; + public QualityProfileServiceData ApiFetchOutput { get; set; } = default!; + public QualityProfileTransactionData TransactionOutput { get; set; } = default!; +} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileStatCalculator.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileStatCalculator.cs similarity index 97% rename from src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileStatCalculator.cs rename to src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileStatCalculator.cs index 03bb077c..437bedb6 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/PipelinePhases/QualityProfileStatCalculator.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileStatCalculator.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.JsonDiffPatch; using Recyclarr.ServarrApi.QualityProfile; -namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; +namespace Recyclarr.Cli.Pipelines.QualityProfile; public record ProfileWithStats { diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileSyncPipeline.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileSyncPipeline.cs deleted file mode 100644 index ba4f39aa..00000000 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/QualityProfileSyncPipeline.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Recyclarr.Cli.Console.Settings; -using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; -using Recyclarr.Config.Models; - -namespace Recyclarr.Cli.Pipelines.QualityProfile; - -public interface IQualityProfilePipelinePhases -{ - QualityProfileConfigPhase ConfigPhase { get; } - QualityProfileApiFetchPhase ApiFetchPhase { get; } - QualityProfileTransactionPhase TransactionPhase { get; } - Lazy PreviewPhase { get; } - QualityProfileApiPersistencePhase ApiPersistencePhase { get; } - QualityProfileNoticePhase NoticePhase { get; } -} - -public class QualityProfileSyncPipeline(ILogger log, IQualityProfilePipelinePhases phases) : ISyncPipeline -{ - public async Task Execute(ISyncSettings settings, IServiceConfiguration config) - { - var guideData = phases.ConfigPhase.Execute(config); - if (guideData.Count == 0) - { - log.Debug("No quality profiles to process"); - return; - } - - var serviceData = await phases.ApiFetchPhase.Execute(config); - var transactions = phases.TransactionPhase.Execute(guideData, serviceData); - - phases.NoticePhase.Execute(transactions); - - if (settings.Preview) - { - phases.PreviewPhase.Value.Execute(transactions); - return; - } - - await phases.ApiPersistencePhase.Execute(config, transactions); - } -} diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs index 9f7157eb..f447feca 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedFormatScore.cs @@ -1,3 +1,4 @@ +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Common.Extensions; using Recyclarr.ServarrApi.QualityProfile; diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfile.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfile.cs index f8575bc2..abc87df2 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfile.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfile.cs @@ -1,3 +1,4 @@ +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.ServarrApi.QualityProfile; diff --git a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfileValidator.cs b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfileValidator.cs index 0cbda71b..4717cab3 100644 --- a/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfileValidator.cs +++ b/src/Recyclarr.Cli/Pipelines/QualityProfile/UpdatedQualityProfileValidator.cs @@ -1,4 +1,5 @@ using FluentValidation; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Common.Extensions; diff --git a/src/Recyclarr.Cli/Pipelines/QualitySize/PipelinePhases/QualitySizeLogPhase.cs b/src/Recyclarr.Cli/Pipelines/QualitySize/PipelinePhases/QualitySizeLogPhase.cs index a9dc4984..ea46161e 100644 --- a/src/Recyclarr.Cli/Pipelines/QualitySize/PipelinePhases/QualitySizeLogPhase.cs +++ b/src/Recyclarr.Cli/Pipelines/QualitySize/PipelinePhases/QualitySizeLogPhase.cs @@ -21,6 +21,10 @@ public class QualitySizeLogPhase(ILogger log) : ILogPipelinePhase return false; } + public void LogTransactionNotices(TagPipelineContext context) + { + } + public void LogPersistenceResults(TagPipelineContext context) { if (context.TransactionOutput.Any()) diff --git a/tests/Recyclarr.Cli.Tests/NewQp.cs b/tests/Recyclarr.Cli.Tests/NewQp.cs index 20a602bd..48e0921e 100644 --- a/tests/Recyclarr.Cli.Tests/NewQp.cs +++ b/tests/Recyclarr.Cli.Tests/NewQp.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using Recyclarr.Cli.Pipelines.QualityProfile; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile; diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhaseTest.cs index 9b1904fd..089b999e 100644 --- a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhaseTest.cs +++ b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileConfigPhaseTest.cs @@ -1,4 +1,5 @@ using Recyclarr.Cli.Pipelines.CustomFormat.Models; +using Recyclarr.Cli.Pipelines.QualityProfile; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Config.Models; using Recyclarr.Tests.TestLibrary; @@ -40,9 +41,10 @@ public class QualityProfileConfigPhaseTest } }); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEquivalentTo(new[] + context.ConfigOutput.Should().BeEquivalentTo(new[] { NewQp.Processed("test_profile", ("id1", 1, 100), ("id2", 2, 100)) }, @@ -72,9 +74,10 @@ public class QualityProfileConfigPhaseTest } }); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEquivalentTo(new[] + context.ConfigOutput.Should().BeEquivalentTo(new[] { NewQp.Processed("test_profile", ("id1", 1, 100), ("id2", 2, 200)) }, @@ -104,9 +107,10 @@ public class QualityProfileConfigPhaseTest } }); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEquivalentTo(new[] + context.ConfigOutput.Should().BeEquivalentTo(new[] { NewQp.Processed("test_profile") }, @@ -162,9 +166,10 @@ public class QualityProfileConfigPhaseTest } ); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEquivalentTo(new[] + context.ConfigOutput.Should().BeEquivalentTo(new[] { NewQp.Processed("test_profile1", ("id1", 1, 100)), NewQp.Processed("test_profile2", ("id1", 1, 200)) @@ -206,9 +211,10 @@ public class QualityProfileConfigPhaseTest } }; - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEquivalentTo(new[] + context.ConfigOutput.Should().BeEquivalentTo(new[] { NewQp.Processed("test_profile", ("id1", 1, 102), ("id2", 2, 201)) with { @@ -236,9 +242,10 @@ public class QualityProfileConfigPhaseTest } }); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEmpty(); + context.ConfigOutput.Should().BeEmpty(); } [Test, AutoMockData] @@ -258,8 +265,9 @@ public class QualityProfileConfigPhaseTest QualityProfiles = Array.Empty() }); - var result = sut.Execute(config); + var context = new QualityProfilePipelineContext(); + sut.Execute(context, config); - result.Should().BeEmpty(); + context.ConfigOutput.Should().BeEmpty(); } } diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs index 584cd13b..ee094ba2 100644 --- a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs +++ b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/PipelinePhases/QualityProfileTransactionPhaseTest.cs @@ -1,4 +1,5 @@ using Recyclarr.Cli.Pipelines.QualityProfile; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile; @@ -9,37 +10,42 @@ namespace Recyclarr.Cli.Tests.Pipelines.QualityProfile.PipelinePhases; public class QualityProfileTransactionPhaseTest { [Test, AutoMockData] - public void Non_existent_profile_names_with_updated( + public void Non_existent_profile_names_mixed_with_valid_profiles( QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed("invalid_profile_name") with - { - ShouldCreate = false - }, - NewQp.Processed("profile1") - }; - var dtos = new[] { new QualityProfileDto {Name = "profile1"} }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed("invalid_profile_name") with + { + ShouldCreate = false + }, + NewQp.Processed("profile1") + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.Should().BeEquivalentTo(new QualityProfileTransactionData + context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData { NonExistentProfiles = new[] {"invalid_profile_name"}, - UpdatedProfiles = + UnchangedProfiles = new List { - new UpdatedQualityProfile + new() { - ProfileConfig = guideData[1], - ProfileDto = dtos[0], - UpdateReason = QualityProfileUpdateReason.Changed + Profile = new UpdatedQualityProfile + { + ProfileConfig = context.ConfigOutput[1], + ProfileDto = dtos[0], + UpdateReason = QualityProfileUpdateReason.Changed + } } } }); @@ -49,59 +55,65 @@ public class QualityProfileTransactionPhaseTest public void New_profiles( QualityProfileTransactionPhase sut) { - var configData = new[] - { - new ProcessedQualityProfileData - { - Profile = new QualityProfileConfig - { - Name = "profile1", - Qualities = new[] - { - new QualityProfileQualityConfig {Name = "quality1", Enabled = true} - } - } - } - }; - var dtos = new[] { new QualityProfileDto {Name = "irrelevant_profile"} }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()) + var context = new QualityProfilePipelineContext { - Schema = new QualityProfileDto + ConfigOutput = new[] { - Items = new[] + new ProcessedQualityProfileData { - new ProfileItemDto {Quality = new ProfileItemQualityDto {Name = "quality1"}} + Profile = new QualityProfileConfig + { + Name = "profile1", + Qualities = new[] + { + new QualityProfileQualityConfig {Name = "quality1", Enabled = true} + } + } + } + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + { + Schema = new QualityProfileDto + { + Items = new[] + { + new ProfileItemDto {Quality = new ProfileItemQualityDto {Name = "quality1"}} + } } } }; - var result = sut.Execute(configData, serviceData); + sut.Execute(context); - result.Should().BeEquivalentTo(new QualityProfileTransactionData + context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData { - UpdatedProfiles = + ChangedProfiles = new List { - new UpdatedQualityProfile + new() { - ProfileConfig = configData[0], - ProfileDto = serviceData.Schema, - UpdateReason = QualityProfileUpdateReason.New, - UpdatedQualities = new UpdatedQualities + QualitiesChanged = true, + Profile = new UpdatedQualityProfile { - NumWantedItems = 1, - Items = new[] + ProfileConfig = context.ConfigOutput[0], + ProfileDto = context.ApiFetchOutput.Schema, + UpdateReason = QualityProfileUpdateReason.New, + UpdatedQualities = new UpdatedQualities { - new ProfileItemDto + NumWantedItems = 1, + Items = new[] { - Allowed = true, - Quality = new ProfileItemQualityDto + new ProfileItemDto { - Name = "quality1" + Allowed = true, + Quality = new ProfileItemQualityDto + { + Name = "quality1" + } } } } @@ -115,11 +127,6 @@ public class QualityProfileTransactionPhaseTest public void Updated_scores( QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed("profile1", ("id1", 1, 100), ("id2", 2, 500)) - }; - var dtos = new[] { new QualityProfileDto @@ -143,12 +150,19 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed("profile1", ("id1", 1, 100), ("id2", 2, 500)) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.UpdatedScores.Should() + context.TransactionOutput.ChangedProfiles.Should() + .ContainSingle().Which.Profile.UpdatedScores.Should() .BeEquivalentTo(new[] { NewQp.UpdatedScore("quality1", 200, 100, FormatScoreUpdateReason.Updated), @@ -160,8 +174,6 @@ public class QualityProfileTransactionPhaseTest public void No_updated_profiles_when_no_custom_formats( QualityProfileTransactionPhase sut) { - var guideData = Array.Empty(); - var dtos = new[] { new QualityProfileDto @@ -185,24 +197,21 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = Array.Empty(), + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.Should().BeEquivalentTo(new QualityProfileTransactionData()); + context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData()); } [Test, AutoMockData] public void Unchanged_scores( QualityProfileTransactionPhase sut) { - // Must simulate at least 1 custom format coming from configuration otherwise processing doesn't happen. - // Profile name must match but the format IDs for each quality should not - var guideData = new[] - { - NewQp.Processed("profile1", ("id1", 1, 200), ("id2", 2, 300)) - }; - var dtos = new[] { new QualityProfileDto @@ -226,12 +235,21 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + // Must simulate at least 1 custom format coming from configuration otherwise processing doesn't happen. + // Profile name must match but the format IDs for each quality should not + ConfigOutput = new[] + { + NewQp.Processed("profile1", ("id1", 1, 200), ("id2", 2, 300)) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.UpdatedScores.Should() + context.TransactionOutput.UnchangedProfiles.Should() + .ContainSingle().Which.Profile.UpdatedScores.Should() .BeEquivalentTo(new[] { NewQp.UpdatedScore("quality1", 200, 200, FormatScoreUpdateReason.NoChange), @@ -243,11 +261,6 @@ public class QualityProfileTransactionPhaseTest public void Reset_scores_with_reset_unmatched_true( QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed("profile1", true, ("quality3", "id3", 3, 100), ("quality4", "id4", 4, 500)) - }; - var dtos = new[] { new QualityProfileDto @@ -271,12 +284,19 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed("profile1", true, ("quality3", "id3", 3, 100), ("quality4", "id4", 4, 500)) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.UpdatedScores.Should() + context.TransactionOutput.ChangedProfiles.Should() + .ContainSingle().Which.Profile.UpdatedScores.Should() .BeEquivalentTo(new[] { NewQp.UpdatedScore("quality1", 200, 0, FormatScoreUpdateReason.Reset), @@ -289,22 +309,6 @@ public class QualityProfileTransactionPhaseTest [Test, AutoMockData] public void Reset_scores_with_reset_unmatched_false(QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed(new QualityProfileConfig - { - Name = "profile1", - ResetUnmatchedScores = new ResetUnmatchedScoresConfig - { - Enabled = false, - // Throw in some exceptions here, just to test whether or not these somehow affect the result - // despite Enable being set to false. - Except = new[] {"cf1"} - } - }, - ("cf3", "id3", 3, 100), ("cf4", "id4", 4, 500)) - }; - var dtos = new[] { new QualityProfileDto @@ -328,12 +332,30 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed(new QualityProfileConfig + { + Name = "profile1", + ResetUnmatchedScores = new ResetUnmatchedScoresConfig + { + Enabled = false, + // Throw in some exceptions here, just to test whether or not these somehow affect the + // result despite Enable being set to false. + Except = new[] {"cf1"} + } + }, + ("cf3", "id3", 3, 100), ("cf4", "id4", 4, 500)) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.UpdatedScores.Should() + context.TransactionOutput.ChangedProfiles.Should() + .ContainSingle().Which.Profile.UpdatedScores.Should() .BeEquivalentTo(new[] { NewQp.UpdatedScore("cf1", 200, 200, FormatScoreUpdateReason.NoChange), @@ -346,20 +368,6 @@ public class QualityProfileTransactionPhaseTest [Test, AutoMockData] public void Reset_scores_with_reset_exceptions(QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed(new QualityProfileConfig - { - Name = "profile1", - ResetUnmatchedScores = new ResetUnmatchedScoresConfig - { - Enabled = true, - Except = new[] {"cf1"} - } - }, - ("cf3", "id3", 3, 100), ("cf4", "id4", 4, 500)) - }; - var dtos = new[] { new QualityProfileDto @@ -383,12 +391,28 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed(new QualityProfileConfig + { + Name = "profile1", + ResetUnmatchedScores = new ResetUnmatchedScoresConfig + { + Enabled = true, + Except = new[] {"cf1"} + } + }, + ("cf3", "id3", 3, 100), ("cf4", "id4", 4, 500)) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.UpdatedScores.Should() + context.TransactionOutput.ChangedProfiles.Should() + .ContainSingle().Which.Profile.UpdatedScores.Should() .BeEquivalentTo(new[] { NewQp.UpdatedScore("cf1", 200, 200, FormatScoreUpdateReason.NoChange), @@ -401,19 +425,6 @@ public class QualityProfileTransactionPhaseTest [Test, AutoMockData] public void Reset_scores_with_invalid_except_list_items(QualityProfileTransactionPhase sut) { - var guideData = new[] - { - NewQp.Processed(new QualityProfileConfig - { - Name = "profile1", - ResetUnmatchedScores = new ResetUnmatchedScoresConfig - { - Enabled = true, - Except = new[] {"cf50"} - } - }) - }; - var dtos = new[] { new QualityProfileDto @@ -437,12 +448,27 @@ public class QualityProfileTransactionPhaseTest } }; - var serviceData = new QualityProfileServiceData(dtos, new QualityProfileDto()); + var context = new QualityProfilePipelineContext + { + ConfigOutput = new[] + { + NewQp.Processed(new QualityProfileConfig + { + Name = "profile1", + ResetUnmatchedScores = new ResetUnmatchedScoresConfig + { + Enabled = true, + Except = new[] {"cf50"} + } + }) + }, + ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto()) + }; - var result = sut.Execute(guideData, serviceData); + sut.Execute(context); - result.UpdatedProfiles.Should() - .ContainSingle().Which.InvalidExceptCfNames.Should() + context.TransactionOutput.ChangedProfiles.Should() + .ContainSingle().Which.Profile.InvalidExceptCfNames.Should() .BeEquivalentTo("cf50"); } } diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileTest.cs index 5193525b..6161309f 100644 --- a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileTest.cs +++ b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileTest.cs @@ -1,4 +1,5 @@ using Recyclarr.Cli.Pipelines.QualityProfile; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile; diff --git a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileValidatorTest.cs b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileValidatorTest.cs index 64c2aa4b..d7758b51 100644 --- a/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileValidatorTest.cs +++ b/tests/Recyclarr.Cli.Tests/Pipelines/QualityProfile/UpdatedQualityProfileValidatorTest.cs @@ -1,5 +1,6 @@ using FluentValidation.TestHelper; using Recyclarr.Cli.Pipelines.QualityProfile; +using Recyclarr.Cli.Pipelines.QualityProfile.Models; using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; using Recyclarr.Config.Models; using Recyclarr.ServarrApi.QualityProfile;