refactor: Make custom format pipeline generic

spectre-console-remove-di-hacks
Robert Dailey 6 months ago
parent b14787e471
commit f4857c7050

@ -81,7 +81,7 @@ public static class CompositionRoot
// ORDER HERE IS IMPORTANT!
// There are indirect dependencies between pipelines.
typeof(GenericSyncPipeline<TagPipelineContext>),
typeof(CustomFormatSyncPipeline),
typeof(GenericSyncPipeline<CustomFormatPipelineContext>),
typeof(GenericSyncPipeline<QualityProfilePipelineContext>),
typeof(GenericSyncPipeline<QualitySizePipelineContext>),
typeof(GenericSyncPipeline<ReleaseProfilePipelineContext>),

@ -1,3 +1,4 @@
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.Cache;

@ -1,5 +1,4 @@
using Autofac;
using Autofac.Extras.AggregateService;
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
@ -10,16 +9,21 @@ public class CustomFormatAutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<ProcessedCustomFormatCache>().As<IPipelineCache>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType<ProcessedCustomFormatCache>()
.As<IPipelineCache>()
.AsSelf()
.InstancePerLifetimeScope();
builder.RegisterType<CustomFormatCachePersister>().As<ICustomFormatCachePersister>();
builder.RegisterType<CustomFormatDataLister>();
builder.RegisterAggregateService<ICustomFormatPipelinePhases>();
builder.RegisterType<CustomFormatConfigPhase>();
builder.RegisterType<CustomFormatApiFetchPhase>();
builder.RegisterType<CustomFormatTransactionPhase>();
builder.RegisterType<CustomFormatPreviewPhase>();
builder.RegisterType<CustomFormatApiPersistencePhase>();
builder.RegisterTypes(
typeof(CustomFormatConfigPhase),
typeof(CustomFormatApiFetchPhase),
typeof(CustomFormatTransactionPhase),
typeof(CustomFormatPreviewPhase),
typeof(CustomFormatApiPersistencePhase),
typeof(CustomFormatLogPhase))
.AsImplementedInterfaces();
}
}

@ -0,0 +1,23 @@
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Common;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat;
public class CustomFormatPipelineContext : IPipelineContext
{
public string PipelineDescription => "Custom Format Pipeline";
public IReadOnlyCollection<SupportedServices> SupportedServiceTypes { get; } = new[]
{
SupportedServices.Sonarr,
SupportedServices.Radarr
};
public IList<CustomFormatData> ConfigOutput { get; init; } = [];
public IList<CustomFormatData> ApiFetchOutput { get; init; } = [];
public CustomFormatTransactionData TransactionOutput { get; set; } = default!;
public IReadOnlyCollection<string> InvalidFormats { get; set; } = default!;
public CustomFormatCache Cache { get; set; } = default!;
}

@ -1,65 +0,0 @@
using System.Collections.ObjectModel;
using Recyclarr.Cli.Console.Settings;
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat;
public interface ICustomFormatPipelinePhases
{
CustomFormatConfigPhase ConfigPhase { get; }
CustomFormatApiFetchPhase ApiFetchPhase { get; }
CustomFormatTransactionPhase TransactionPhase { get; }
CustomFormatPreviewPhase PreviewPhase { get; }
CustomFormatApiPersistencePhase ApiPersistencePhase { get; }
}
public record CustomFormatTransactionData
{
public Collection<TrashIdMapping> DeletedCustomFormats { get; } = new();
public Collection<CustomFormatData> NewCustomFormats { get; } = new();
public Collection<CustomFormatData> UpdatedCustomFormats { get; } = new();
public Collection<ConflictingCustomFormat> ConflictingCustomFormats { get; } = new();
public Collection<CustomFormatData> UnchangedCustomFormats { get; } = new();
}
public class CustomFormatSyncPipeline(
ILogger log,
ICustomFormatCachePersister cachePersister,
ICustomFormatPipelinePhases phases)
: ISyncPipeline
{
public async Task Execute(ISyncSettings settings, IServiceConfiguration config)
{
var cache = cachePersister.Load(config);
var guideCfs = phases.ConfigPhase.Execute(config);
if (guideCfs.IsEmpty())
{
log.Debug("No custom formats to process");
return;
}
var serviceData = await phases.ApiFetchPhase.Execute(config);
cache.RemoveStale(serviceData);
var transactions = phases.TransactionPhase.Execute(config, guideCfs, serviceData, cache);
phases.PreviewPhase.Execute(transactions);
if (settings.Preview)
{
return;
}
await phases.ApiPersistencePhase.Execute(config, transactions);
cache.Update(transactions);
cachePersister.Save(config, cache);
}
}

@ -0,0 +1,14 @@
using System.Collections.ObjectModel;
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.Models;
public record CustomFormatTransactionData
{
public Collection<TrashIdMapping> DeletedCustomFormats { get; } = new();
public Collection<CustomFormatData> NewCustomFormats { get; } = new();
public Collection<CustomFormatData> UpdatedCustomFormats { get; } = new();
public Collection<ConflictingCustomFormat> ConflictingCustomFormats { get; } = new();
public Collection<CustomFormatData> UnchangedCustomFormats { get; } = new();
}

@ -1,14 +1,17 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.CustomFormat;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatApiFetchPhase(ICustomFormatApiService api)
: IApiFetchPipelinePhase<CustomFormatPipelineContext>
{
public async Task<IReadOnlyCollection<CustomFormatData>> Execute(IServiceConfiguration config)
public async Task Execute(CustomFormatPipelineContext context, IServiceConfiguration config)
{
var result = await api.GetCustomFormats(config);
return result.AsReadOnly();
context.ApiFetchOutput.AddRange(result);
context.Cache.RemoveStale(result);
}
}

@ -1,12 +1,17 @@
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatApiPersistencePhase(ICustomFormatApiService api)
public class CustomFormatApiPersistencePhase(ICustomFormatApiService api, ICustomFormatCachePersister cachePersister)
: IApiPersistencePipelinePhase<CustomFormatPipelineContext>
{
public async Task Execute(IServiceConfiguration config, CustomFormatTransactionData transactions)
public async Task Execute(CustomFormatPipelineContext context, IServiceConfiguration config)
{
var transactions = context.TransactionOutput;
foreach (var cf in transactions.NewCustomFormats)
{
var response = await api.CreateCustomFormat(config, cf);
@ -25,5 +30,8 @@ public class CustomFormatApiPersistencePhase(ICustomFormatApiService api)
{
await api.DeleteCustomFormat(config, map.CustomFormatId);
}
context.Cache.Update(transactions);
cachePersister.Save(config, context.Cache);
}
}

@ -1,13 +1,19 @@
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatConfigPhase(ILogger log, ICustomFormatGuideService guide, ProcessedCustomFormatCache cache)
public class CustomFormatConfigPhase(
ICustomFormatGuideService guide,
ProcessedCustomFormatCache cache,
ICustomFormatCachePersister cachePersister)
: IConfigPipelinePhase<CustomFormatPipelineContext>
{
public IReadOnlyCollection<CustomFormatData> Execute(IServiceConfiguration config)
public Task Execute(CustomFormatPipelineContext context, IServiceConfiguration config)
{
// Match custom formats in the YAML config to those in the guide, by Trash ID
//
@ -25,14 +31,11 @@ public class CustomFormatConfigPhase(ILogger log, ICustomFormatGuideService guid
(id, cf) => (Id: id, CustomFormats: cf))
.ToLookup(x => x.Item2.Any());
var invalidCfs = processedCfs[false].Select(x => x.Id).ToList();
if (invalidCfs.IsNotEmpty())
{
log.Warning("These Custom Formats do not exist in the guide and will be skipped: {Cfs}", invalidCfs);
}
context.InvalidFormats = processedCfs[false].Select(x => x.Id).ToList();
context.ConfigOutput.AddRange(processedCfs[true].SelectMany(x => x.CustomFormats));
context.Cache = cachePersister.Load(config);
var validCfs = processedCfs[true].SelectMany(x => x.CustomFormats).ToList();
cache.AddCustomFormats(validCfs);
return validCfs;
cache.AddCustomFormats(context.ConfigOutput);
return Task.CompletedTask;
}
}

@ -0,0 +1,33 @@
using Recyclarr.Cli.Pipelines.Generic;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatLogPhase(ILogger log) : ILogPipelinePhase<CustomFormatPipelineContext>
{
// Returning 'true' means to exit. 'false' means to proceed.
public bool LogConfigPhaseAndExitIfNeeded(CustomFormatPipelineContext context)
{
if (context.InvalidFormats.Count != 0)
{
log.Warning("These Custom Formats do not exist in the guide and will be skipped: {Cfs}",
context.InvalidFormats);
}
if (context.ConfigOutput.Count == 0)
{
log.Debug("No custom formats to process");
return true;
}
return false;
}
public void LogTransactionNotices(CustomFormatPipelineContext context)
{
}
public void LogPersistenceResults(CustomFormatPipelineContext context)
{
// Logging is done (and shared with) in CustomFormatPreviewPhase
}
}

@ -1,9 +1,13 @@
using Recyclarr.Cli.Pipelines.Generic;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatPreviewPhase(ILogger log)
public class CustomFormatPreviewPhase(ILogger log) : IPreviewPipelinePhase<CustomFormatPipelineContext>
{
public void Execute(CustomFormatTransactionData transactions)
public void Execute(CustomFormatPipelineContext context)
{
var transactions = context.TransactionOutput;
foreach (var (guideCf, conflictingId) in transactions.ConflictingCustomFormats)
{
log.Warning(
@ -66,4 +70,5 @@ public class CustomFormatPreviewPhase(ILogger log)
log.Information("All custom formats are already up to date!");
}
}
}

@ -1,37 +1,31 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
public class CustomFormatTransactionPhase(ILogger log)
public class CustomFormatTransactionPhase(ILogger log) : ITransactionPipelinePhase<CustomFormatPipelineContext>
{
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public CustomFormatTransactionData Execute(
IServiceConfiguration config,
IReadOnlyCollection<CustomFormatData> guideCfs,
IReadOnlyCollection<CustomFormatData> serviceData,
CustomFormatCache cache)
public void Execute(CustomFormatPipelineContext context, IServiceConfiguration config)
{
var transactions = new CustomFormatTransactionData();
foreach (var guideCf in guideCfs)
foreach (var guideCf in context.ConfigOutput)
{
log.Debug("Process transaction for guide CF {TrashId} ({Name})", guideCf.TrashId, guideCf.Name);
guideCf.Id = cache.FindId(guideCf) ?? 0;
guideCf.Id = context.Cache.FindId(guideCf) ?? 0;
var serviceCf = FindServiceCfByName(serviceData, guideCf.Name);
var serviceCf = FindServiceCfByName(context.ApiFetchOutput, guideCf.Name);
if (serviceCf is not null)
{
ProcessExistingCf(config, guideCf, serviceCf, transactions);
continue;
}
serviceCf = FindServiceCfById(serviceData, guideCf.Id);
serviceCf = FindServiceCfById(context.ApiFetchOutput, guideCf.Id);
if (serviceCf is not null)
{
// We do not use AddUpdatedCustomFormat() here because it's impossible for the CFs to be identical if we
@ -47,14 +41,14 @@ public class CustomFormatTransactionPhase(ILogger log)
if (config.DeleteOldCustomFormats)
{
transactions.DeletedCustomFormats.AddRange(cache.Mappings
transactions.DeletedCustomFormats.AddRange(context.Cache.Mappings
// Custom format must be in the cache but NOT in the user's config
.Where(map => guideCfs.All(cf => cf.TrashId != map.TrashId))
.Where(map => context.ConfigOutput.All(cf => cf.TrashId != map.TrashId))
// Also, that cache-only CF must exist in the service (otherwise there is nothing to delete)
.Where(map => serviceData.Any(cf => cf.Id == map.CustomFormatId)));
.Where(map => context.ApiFetchOutput.Any(cf => cf.Id == map.CustomFormatId)));
}
return transactions;
context.TransactionOutput = transactions;
}
private void ProcessExistingCf(

@ -13,6 +13,7 @@ public class GenericSyncPipeline<TContext>(ILogger log, GenericPipelinePhases<TC
{
log.Debug("Skipping {Description} because it does not support service type {Service}",
context.PipelineDescription, config.ServiceType);
return;
}
await phases.ConfigPhase.Execute(context, config);
@ -22,7 +23,7 @@ public class GenericSyncPipeline<TContext>(ILogger log, GenericPipelinePhases<TC
}
await phases.ApiFetchPhase.Execute(context, config);
phases.TransactionPhase.Execute(context);
phases.TransactionPhase.Execute(context, config);
phases.LogPhase.LogTransactionNotices(context);

@ -1,7 +1,9 @@
using Recyclarr.Config.Models;
namespace Recyclarr.Cli.Pipelines.Generic;
public interface ITransactionPipelinePhase<in TContext>
where TContext : IPipelineContext
{
void Execute(TContext context);
void Execute(TContext context, IServiceConfiguration config);
}

@ -1,11 +1,12 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
public class MediaNamingTransactionPhase : ITransactionPipelinePhase<MediaNamingPipelineContext>
{
public void Execute(MediaNamingPipelineContext context)
public void Execute(MediaNamingPipelineContext context, IServiceConfiguration config)
{
context.TransactionOutput = context.ApiFetchOutput switch
{

@ -10,7 +10,7 @@ namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases;
public class QualityProfileTransactionPhase(QualityProfileStatCalculator statCalculator)
: ITransactionPipelinePhase<QualityProfilePipelineContext>
{
public void Execute(QualityProfilePipelineContext context)
public void Execute(QualityProfilePipelineContext context, IServiceConfiguration config)
{
var transactions = new QualityProfileTransactionData();

@ -1,5 +1,4 @@
using Recyclarr.Cli.Pipelines.QualityProfile.Models;
using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases;
using Recyclarr.Common.Extensions;
using Recyclarr.ServarrApi.QualityProfile;

@ -1,5 +1,4 @@
using Recyclarr.Cli.Pipelines.QualityProfile.Models;
using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases;
using Recyclarr.ServarrApi.QualityProfile;
namespace Recyclarr.Cli.Pipelines.QualityProfile;

@ -1,6 +1,5 @@
using FluentValidation;
using Recyclarr.Cli.Pipelines.QualityProfile.Models;
using Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases;
using Recyclarr.Common.Extensions;
namespace Recyclarr.Cli.Pipelines.QualityProfile;

@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.QualityDefinition;
using Recyclarr.TrashGuide.QualitySize;
@ -7,7 +8,7 @@ namespace Recyclarr.Cli.Pipelines.QualitySize.PipelinePhases;
public class QualitySizeTransactionPhase(ILogger log) : ITransactionPipelinePhase<QualitySizePipelineContext>
{
public void Execute(QualitySizePipelineContext context)
public void Execute(QualitySizePipelineContext context, IServiceConfiguration config)
{
// Do not check ConfigOutput for null since the LogPhase does it for us
var guideQuality = context.ConfigOutput!.Qualities;

@ -6,7 +6,7 @@ public class ReleaseProfileLogPhase(ILogger log) : ILogPipelinePhase<ReleaseProf
{
public bool LogConfigPhaseAndExitIfNeeded(ReleaseProfilePipelineContext context)
{
if (context.ConfigOutput.Any())
if (context.ConfigOutput is {Count: > 0})
{
return false;
}
@ -26,19 +26,21 @@ public class ReleaseProfileLogPhase(ILogger log) : ILogPipelinePhase<ReleaseProf
if (transactions.UpdatedProfiles.Count != 0)
{
log.Information("Update existing profiles: {ProfileNames}", transactions.UpdatedProfiles);
log.Information("Update existing profiles: {ProfileNames}",
transactions.UpdatedProfiles.Select(x => x.Name));
somethingChanged = true;
}
if (transactions.CreatedProfiles.Count != 0)
{
log.Information("Create new profiles: {ProfileNames}", transactions.CreatedProfiles);
log.Information("Create new profiles: {ProfileNames}", transactions.CreatedProfiles.Select(x => x.Name));
somethingChanged = true;
}
if (transactions.DeletedProfiles.Count != 0)
{
log.Information("Deleting old release profiles: {ProfileNames}", transactions.DeletedProfiles);
log.Information("Deleting old release profiles: {ProfileNames}",
transactions.DeletedProfiles.Select(x => x.Name));
somethingChanged = true;
}

@ -2,6 +2,7 @@ using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Cli.Pipelines.ReleaseProfile.Models;
using Recyclarr.Cli.Pipelines.Tags;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.ReleaseProfile;
namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
@ -9,7 +10,7 @@ namespace Recyclarr.Cli.Pipelines.ReleaseProfile.PipelinePhases;
public class ReleaseProfileTransactionPhase(ServiceTagCache tagCache)
: ITransactionPipelinePhase<ReleaseProfilePipelineContext>
{
public void Execute(ReleaseProfilePipelineContext context)
public void Execute(ReleaseProfilePipelineContext context, IServiceConfiguration config)
{
var created = new List<SonarrReleaseProfile>();
var updated = new List<SonarrReleaseProfile>();

@ -1,11 +1,12 @@
using Recyclarr.Cli.Pipelines.Generic;
using Recyclarr.Common.Extensions;
using Recyclarr.Config.Models;
namespace Recyclarr.Cli.Pipelines.Tags.PipelinePhases;
public class TagTransactionPhase : ITransactionPipelinePhase<TagPipelineContext>
{
public void Execute(TagPipelineContext context)
public void Execute(TagPipelineContext context, IServiceConfiguration config)
{
// List of tags in config that do not already exist in the service. The goal is to figure out which tags need to
// be created.

@ -15,20 +15,18 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var config = NewConfig.Radarr();
var context = new CustomFormatPipelineContext
{
NewCf.Data("one", "cf1")
Cache = new CustomFormatCache([]),
ApiFetchOutput = [],
ConfigOutput = [NewCf.Data("one", "cf1")]
};
var serviceData = Array.Empty<CustomFormatData>();
var cache = new CustomFormatCache([]);
var config = NewConfig.Radarr();
sut.Execute(context, config);
var result = sut.Execute(config, guideCfs, serviceData, cache);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
NewCustomFormats =
{
@ -42,29 +40,27 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
{
new CustomFormatData
{
Name = "one",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
};
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "one"}
Cache = new CustomFormatCache([]),
ApiFetchOutput = [new CustomFormatData {Name = "one"}],
ConfigOutput =
[
new CustomFormatData
{
Name = "one",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
]
};
var cache = new CustomFormatCache([]);
var config = NewConfig.Radarr();
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UpdatedCustomFormats =
{
@ -81,31 +77,27 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
{
new CustomFormatData
{
Name = "different1",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
};
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "different2", Id = 2}
Cache = new CustomFormatCache([new TrashIdMapping("cf1", "", 2)]),
ApiFetchOutput = [new CustomFormatData {Name = "different2", Id = 2}],
ConfigOutput =
[
new CustomFormatData
{
Name = "different1",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
]
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf1", "", 2)
]);
var config = NewConfig.Radarr();
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UpdatedCustomFormats =
{
@ -122,32 +114,30 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
{
new CustomFormatData
{
Name = "different1",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
};
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "different1", Id = 2}
Cache = new CustomFormatCache([]),
ApiFetchOutput = [new CustomFormatData {Name = "different1", Id = 2}],
ConfigOutput =
[
new CustomFormatData
{
Name = "different1",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
]
};
var cache = new CustomFormatCache([]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = true
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UpdatedCustomFormats =
{
@ -164,31 +154,29 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("one", "cf1")
Cache = new CustomFormatCache([]),
ApiFetchOutput =
[
new CustomFormatData {Name = "one", Id = 2},
new CustomFormatData {Name = "two", Id = 1}
],
ConfigOutput = [NewCf.Data("one", "cf1")]
};
var serviceData = new[]
{
new CustomFormatData {Name = "one", Id = 2},
new CustomFormatData {Name = "two", Id = 1}
};
var cache = new CustomFormatCache([]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = false
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
ConflictingCustomFormats =
{
new ConflictingCustomFormat(guideCfs[0], 2)
new ConflictingCustomFormat(context.ConfigOutput[0], 2)
}
});
}
@ -198,33 +186,29 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("one", "cf1")
Cache = new CustomFormatCache([new TrashIdMapping("cf1", "one", 1)]),
ApiFetchOutput =
[
new CustomFormatData {Name = "one", Id = 2},
new CustomFormatData {Name = "two", Id = 1}
],
ConfigOutput = [NewCf.Data("one", "cf1")]
};
var serviceData = new[]
{
new CustomFormatData {Name = "one", Id = 2},
new CustomFormatData {Name = "two", Id = 1}
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf1", "one", 1)
]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = false
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
ConflictingCustomFormats =
{
new ConflictingCustomFormat(guideCfs[0], 2)
new ConflictingCustomFormat(context.ConfigOutput[0], 2)
}
});
}
@ -234,35 +218,34 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
{
new CustomFormatData
{
Name = "one",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
};
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "two", Id = 2},
new CustomFormatData {Name = "one", Id = 1}
Cache = new CustomFormatCache([new TrashIdMapping("cf1", "one", 1)]),
ApiFetchOutput =
[
new CustomFormatData {Name = "two", Id = 2},
new CustomFormatData {Name = "one", Id = 1}
],
ConfigOutput =
[
new CustomFormatData
{
Name = "one",
TrashId = "cf1",
// Only set the below value to make it different from the service CF
IncludeCustomFormatWhenRenaming = true
}
]
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf1", "one", 1)
]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = false
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UpdatedCustomFormats =
{
@ -276,28 +259,23 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("one", "cf1")
Cache = new CustomFormatCache([]),
ApiFetchOutput = [new CustomFormatData {Name = "one", Id = 1}],
ConfigOutput = [NewCf.Data("one", "cf1")]
};
var serviceData = new[]
{
new CustomFormatData {Name = "one", Id = 1}
};
var cache = new CustomFormatCache([]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = true
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UnchangedCustomFormats = {guideCfs[0]}
UnchangedCustomFormats = {context.ConfigOutput[0]}
});
}
@ -306,30 +284,23 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("one", "cf1")
Cache = new CustomFormatCache([new TrashIdMapping("cf1", "one", 1)]),
ApiFetchOutput = [new CustomFormatData {Name = "one", Id = 1}],
ConfigOutput = [NewCf.Data("one", "cf1")]
};
var serviceData = new[]
{
new CustomFormatData {Name = "one", Id = 1}
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf1", "one", 1)
]);
var config = NewConfig.Radarr() with
{
ReplaceExistingCustomFormats = false
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
UnchangedCustomFormats = {guideCfs[0]}
UnchangedCustomFormats = {context.ConfigOutput[0]}
});
}
@ -338,25 +309,21 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = Array.Empty<CustomFormatData>();
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "two", Id = 2}
Cache = new CustomFormatCache([new TrashIdMapping("cf2", "two", 2)]),
ApiFetchOutput = [new CustomFormatData {Name = "two", Id = 2}],
ConfigOutput = []
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf2", "two", 2)
]);
var config = NewConfig.Radarr() with
{
DeleteOldCustomFormats = true
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
DeletedCustomFormats =
{
@ -370,25 +337,21 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = Array.Empty<CustomFormatData>();
var serviceData = new[]
var context = new CustomFormatPipelineContext
{
new CustomFormatData {Name = "two", Id = 2}
Cache = new CustomFormatCache([new TrashIdMapping("cf2", "two", 2)]),
ApiFetchOutput = [new CustomFormatData {Name = "two", Id = 2}],
ConfigOutput = []
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf2", "two", 2)
]);
var config = NewConfig.Radarr() with
{
DeleteOldCustomFormats = false
};
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData());
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData());
}
[Test]
@ -396,25 +359,18 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("two", "cf2", 2)
Cache = new CustomFormatCache([new TrashIdMapping("cf2", "two", 2)]),
ApiFetchOutput = [new CustomFormatData {Name = "two", Id = 2}],
ConfigOutput = [NewCf.Data("two", "cf2", 2)]
};
var serviceData = new[]
{
new CustomFormatData {Name = "two", Id = 2}
};
var cache = new CustomFormatCache([
new TrashIdMapping("cf2", "two", 2)
]);
var config = NewConfig.Radarr();
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.DeletedCustomFormats.Should().BeEmpty();
context.TransactionOutput.DeletedCustomFormats.Should().BeEmpty();
}
[Test]
@ -422,22 +378,18 @@ internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture
{
var sut = Resolve<CustomFormatTransactionPhase>();
var guideCfs = new[]
var context = new CustomFormatPipelineContext
{
NewCf.Data("two", "cf2", 2)
Cache = new CustomFormatCache([new TrashIdMapping("cf2", "two", 200)]),
ApiFetchOutput = [],
ConfigOutput = [NewCf.Data("two", "cf2", 2)]
};
var serviceData = Array.Empty<CustomFormatData>();
var cache = new CustomFormatCache([
new TrashIdMapping("cf2", "two", 200)
]);
var config = NewConfig.Radarr();
var result = sut.Execute(config, guideCfs, serviceData, cache);
sut.Execute(context, config);
result.Should().BeEquivalentTo(new CustomFormatTransactionData
context.TransactionOutput.Should().BeEquivalentTo(new CustomFormatTransactionData
{
NewCustomFormats =
{

@ -1,5 +1,5 @@
using Recyclarr.Cli.Pipelines.CustomFormat;
using Recyclarr.Cli.Pipelines.CustomFormat.Cache;
using Recyclarr.Cli.Pipelines.CustomFormat.Models;
using Recyclarr.Tests.TestLibrary;
namespace Recyclarr.Cli.Tests.Cache;

@ -1,7 +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;

@ -1,3 +1,4 @@
using Recyclarr.Cli.Pipelines.CustomFormat;
using Recyclarr.Cli.Pipelines.CustomFormat.PipelinePhases;
using Recyclarr.Config.Models;
using Recyclarr.Tests.TestLibrary;
@ -34,9 +35,11 @@ public class CustomFormatConfigPhaseTest
}
};
var result = sut.Execute(config);
var context = new CustomFormatPipelineContext();
result.Should().BeEquivalentTo(new[]
sut.Execute(context, config);
context.ConfigOutput.Should().BeEquivalentTo(new[]
{
NewCf.Data("one", "cf1"),
NewCf.Data("two", "cf2")
@ -69,8 +72,10 @@ public class CustomFormatConfigPhaseTest
}
};
var result = sut.Execute(config);
var context = new CustomFormatPipelineContext();
sut.Execute(context, config);
result.Should().BeEmpty();
context.ConfigOutput.Should().BeEmpty();
}
}

@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Cli.Pipelines.MediaNaming;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming;
@ -28,7 +29,7 @@ public class MediaNamingTransactionPhaseRadarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ConfigOutput.Dto, o => o.RespectingRuntimeTypes());
}
@ -51,7 +52,7 @@ public class MediaNamingTransactionPhaseRadarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ApiFetchOutput, o => o.RespectingRuntimeTypes());
}
@ -79,7 +80,7 @@ public class MediaNamingTransactionPhaseRadarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ConfigOutput.Dto, o => o.RespectingRuntimeTypes());
}
@ -107,7 +108,7 @@ public class MediaNamingTransactionPhaseRadarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new RadarrMediaNamingDto
{

@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using Recyclarr.Cli.Pipelines.MediaNaming;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming;
@ -30,7 +31,7 @@ public class MediaNamingTransactionPhaseSonarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ConfigOutput.Dto, o => o.RespectingRuntimeTypes());
}
@ -56,7 +57,7 @@ public class MediaNamingTransactionPhaseSonarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ApiFetchOutput, o => o.RespectingRuntimeTypes());
}
@ -90,7 +91,7 @@ public class MediaNamingTransactionPhaseSonarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(context.ConfigOutput.Dto, o => o.RespectingRuntimeTypes());
}
@ -124,7 +125,7 @@ public class MediaNamingTransactionPhaseSonarrTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new SonarrMediaNamingDto
{

@ -31,7 +31,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData
{
@ -88,7 +88,7 @@ public class QualityProfileTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData
{
@ -159,7 +159,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.ChangedProfiles.Should()
.ContainSingle().Which.Profile.UpdatedScores.Should()
@ -203,7 +203,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new QualityProfileTransactionData());
}
@ -246,7 +246,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.UnchangedProfiles.Should()
.ContainSingle().Which.Profile.UpdatedScores.Should()
@ -293,7 +293,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.ChangedProfiles.Should()
.ContainSingle().Which.Profile.UpdatedScores.Should()
@ -352,7 +352,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.ChangedProfiles.Should()
.ContainSingle().Which.Profile.UpdatedScores.Should()
@ -409,7 +409,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.ChangedProfiles.Should()
.ContainSingle().Which.Profile.UpdatedScores.Should()
@ -465,7 +465,7 @@ public class QualityProfileTransactionPhaseTest
ApiFetchOutput = new QualityProfileServiceData(dtos, new QualityProfileDto())
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.ChangedProfiles.Should()
.ContainSingle().Which.Profile.InvalidExceptCfNames.Should()

@ -1,6 +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;

@ -1,7 +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;

@ -1,5 +1,6 @@
using Recyclarr.Cli.Pipelines.QualitySize;
using Recyclarr.Cli.Pipelines.QualitySize.PipelinePhases;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.QualityDefinition;
using Recyclarr.TrashGuide.QualitySize;
@ -31,7 +32,7 @@ public class QualitySizeTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEmpty();
}
@ -69,7 +70,7 @@ public class QualitySizeTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEmpty();
}
@ -107,7 +108,7 @@ public class QualitySizeTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo(new List<ServiceQualityDefinitionItem>
{

@ -1,5 +1,6 @@
using Recyclarr.Cli.Pipelines.Tags;
using Recyclarr.Cli.Pipelines.Tags.PipelinePhases;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.Tag;
namespace Recyclarr.Cli.Tests.Pipelines.Tags.PipelinePhases;
@ -20,7 +21,7 @@ public class TagTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo("one", "two");
}
@ -34,7 +35,7 @@ public class TagTransactionPhaseTest
ApiFetchOutput = Array.Empty<SonarrTag>()
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEquivalentTo("one", "two", "three");
}
@ -52,7 +53,7 @@ public class TagTransactionPhaseTest
}
};
sut.Execute(context);
sut.Execute(context, Substitute.For<IServiceConfiguration>());
context.TransactionOutput.Should().BeEmpty();
}

Loading…
Cancel
Save