refactor: Strategy pattern for MediaNamingConfigPhase

pull/231/head
Robert Dailey 1 year ago
parent 7b5bb5891a
commit 55daad8db1

@ -1,6 +1,8 @@
using Autofac;
using Autofac.Extras.AggregateService;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
using Recyclarr.Common;
namespace Recyclarr.Cli.Pipelines.MediaNaming;
@ -11,6 +13,11 @@ public class MediaNamingAutofacModule : Module
base.Load(builder);
builder.RegisterType<MediaNamingDataLister>();
builder.RegisterType<RadarrMediaNamingConfigPhase>()
.Keyed<IServiceBasedMediaNamingConfigPhase>(SupportedServices.Radarr);
builder.RegisterType<SonarrMediaNamingConfigPhase>()
.Keyed<IServiceBasedMediaNamingConfigPhase>(SupportedServices.Sonarr);
builder.RegisterAggregateService<IMediaNamingPipelinePhases>();
builder.RegisterType<MediaNamingConfigPhase>();
builder.RegisterType<MediaNamingApiFetchPhase>();

@ -0,0 +1,13 @@
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
public interface IServiceBasedMediaNamingConfigPhase
{
Task<MediaNamingDto> ProcessNaming(
IServiceConfiguration config,
IMediaNamingGuideService guide,
NamingFormatLookup lookup);
}

@ -0,0 +1,49 @@
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
public class NamingFormatLookup
{
private readonly List<InvalidNamingConfig> _errors = new();
public IReadOnlyCollection<InvalidNamingConfig> Errors => _errors;
public string? ObtainFormat(
IReadOnlyDictionary<string, string> guideFormats,
string? configFormatKey,
string errorDescription)
{
return ObtainFormat(guideFormats, configFormatKey, null, errorDescription);
}
public string? ObtainFormat(
IReadOnlyDictionary<string, string> guideFormats,
string? configFormatKey,
string? keySuffix,
string errorDescription)
{
if (configFormatKey is null)
{
return null;
}
// Use lower-case for the config value because System.Text.Json doesn't let us create a case-insensitive
// dictionary. The MediaNamingGuideService converts all parsed guide JSON keys to lower case.
var lowerKey = configFormatKey.ToLowerInvariant();
var keys = new List<string> {lowerKey};
if (keySuffix is not null)
{
// Put the more specific key first
keys.Insert(0, lowerKey + keySuffix);
}
foreach (var k in keys)
{
if (guideFormats.TryGetValue(k, out var format))
{
return format;
}
}
_errors.Add(new InvalidNamingConfig(errorDescription, configFormatKey));
return null;
}
}

@ -0,0 +1,25 @@
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
public class RadarrMediaNamingConfigPhase : ServiceBasedMediaNamingConfigPhase<RadarrConfiguration>
{
protected override Task<MediaNamingDto> ProcessNaming(
RadarrConfiguration config,
IMediaNamingGuideService guide,
NamingFormatLookup lookup)
{
var guideData = guide.GetRadarrNamingData();
var configData = config.MediaNaming;
return Task.FromResult<MediaNamingDto>(new RadarrMediaNamingDto
{
StandardMovieFormat =
lookup.ObtainFormat(guideData.File, configData.Movie?.Standard, "Standard Movie Format"),
MovieFolderFormat = lookup.ObtainFormat(guideData.Folder, configData.Folder, "Movie Folder Format"),
RenameMovies = configData.Movie?.Rename
});
}
}

@ -0,0 +1,22 @@
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
public abstract class ServiceBasedMediaNamingConfigPhase<TConfig> : IServiceBasedMediaNamingConfigPhase
where TConfig : IServiceConfiguration
{
public Task<MediaNamingDto> ProcessNaming(
IServiceConfiguration config,
IMediaNamingGuideService guide,
NamingFormatLookup lookup)
{
return ProcessNaming((TConfig) config, guide, lookup);
}
protected abstract Task<MediaNamingDto> ProcessNaming(
TConfig config,
IMediaNamingGuideService guide,
NamingFormatLookup lookup);
}

@ -0,0 +1,49 @@
using Recyclarr.Compatibility.Sonarr;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
public class SonarrMediaNamingConfigPhase : ServiceBasedMediaNamingConfigPhase<SonarrConfiguration>
{
private readonly ISonarrCapabilityFetcher _sonarrCapabilities;
public SonarrMediaNamingConfigPhase(ISonarrCapabilityFetcher sonarrCapabilities)
{
_sonarrCapabilities = sonarrCapabilities;
}
protected override async Task<MediaNamingDto> ProcessNaming(
SonarrConfiguration config,
IMediaNamingGuideService guide,
NamingFormatLookup lookup)
{
var guideData = guide.GetSonarrNamingData();
var configData = config.MediaNaming;
var capabilities = await _sonarrCapabilities.GetCapabilities(config);
var keySuffix = capabilities.SupportsCustomFormats ? ":4" : ":3";
return new SonarrMediaNamingDto
{
SeasonFolderFormat = lookup.ObtainFormat(guideData.Season, configData.Season, "Season Folder Format"),
SeriesFolderFormat = lookup.ObtainFormat(guideData.Series, configData.Series, "Series Folder Format"),
StandardEpisodeFormat = lookup.ObtainFormat(
guideData.Episodes.Standard,
configData.Episodes?.Standard,
keySuffix,
"Standard Episode Format"),
DailyEpisodeFormat = lookup.ObtainFormat(
guideData.Episodes.Daily,
configData.Episodes?.Daily,
keySuffix,
"Daily Episode Format"),
AnimeEpisodeFormat = lookup.ObtainFormat(
guideData.Episodes.Anime,
configData.Episodes?.Anime,
keySuffix,
"Anime Episode Format"),
RenameEpisodes = configData.Episodes?.Rename
};
}
}

@ -1,4 +1,6 @@
using Recyclarr.Compatibility.Sonarr;
using Autofac.Features.Indexed;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
using Recyclarr.Common;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
@ -13,106 +15,16 @@ public record ProcessedNamingConfig
public IReadOnlyCollection<InvalidNamingConfig> InvalidNaming { get; init; } = new List<InvalidNamingConfig>();
}
public class MediaNamingConfigPhase(IMediaNamingGuideService guide, ISonarrCapabilityFetcher sonarrCapabilities)
public class MediaNamingConfigPhase(
IMediaNamingGuideService guide,
IIndex<SupportedServices, IServiceBasedMediaNamingConfigPhase> configPhaseStrategyFactory)
{
private List<InvalidNamingConfig> _errors = new();
public async Task<ProcessedNamingConfig> Execute(IServiceConfiguration config)
{
_errors = new List<InvalidNamingConfig>();
var dto = config switch
{
RadarrConfiguration c => ProcessRadarrNaming(c),
SonarrConfiguration c => await ProcessSonarrNaming(c),
_ => throw new ArgumentException("Configuration type unsupported for naming sync")
};
return new ProcessedNamingConfig {Dto = dto, InvalidNaming = _errors};
}
private RadarrMediaNamingDto ProcessRadarrNaming(RadarrConfiguration config)
{
var guideData = guide.GetRadarrNamingData();
var configData = config.MediaNaming;
return new RadarrMediaNamingDto
{
StandardMovieFormat = ObtainFormat(guideData.File, configData.Movie?.Standard, "Standard Movie Format"),
MovieFolderFormat = ObtainFormat(guideData.Folder, configData.Folder, "Movie Folder Format"),
RenameMovies = configData.Movie?.Rename
};
}
private async Task<MediaNamingDto> ProcessSonarrNaming(SonarrConfiguration config)
{
var guideData = guide.GetSonarrNamingData();
var configData = config.MediaNaming;
var capabilities = await sonarrCapabilities.GetCapabilities(config);
var keySuffix = capabilities.SupportsCustomFormats ? ":4" : ":3";
return new SonarrMediaNamingDto
{
SeasonFolderFormat = ObtainFormat(guideData.Season, configData.Season, "Season Folder Format"),
SeriesFolderFormat = ObtainFormat(guideData.Series, configData.Series, "Series Folder Format"),
StandardEpisodeFormat = ObtainFormat(
guideData.Episodes.Standard,
configData.Episodes?.Standard,
keySuffix,
"Standard Episode Format"),
DailyEpisodeFormat = ObtainFormat(
guideData.Episodes.Daily,
configData.Episodes?.Daily,
keySuffix,
"Daily Episode Format"),
AnimeEpisodeFormat = ObtainFormat(
guideData.Episodes.Anime,
configData.Episodes?.Anime,
keySuffix,
"Anime Episode Format"),
RenameEpisodes = configData.Episodes?.Rename
};
}
private string? ObtainFormat(
IReadOnlyDictionary<string, string> guideFormats,
string? configFormatKey,
string errorDescription)
{
return ObtainFormat(guideFormats, configFormatKey, null, errorDescription);
}
private string? ObtainFormat(
IReadOnlyDictionary<string, string> guideFormats,
string? configFormatKey,
string? keySuffix,
string errorDescription)
{
if (configFormatKey is null)
{
return null;
}
// Use lower-case for the config value because System.Text.Json doesn't let us create a case-insensitive
// dictionary. The MediaNamingGuideService converts all parsed guide JSON keys to lower case.
var lowerKey = configFormatKey.ToLowerInvariant();
var keys = new List<string> {lowerKey};
if (keySuffix is not null)
{
// Put the more specific key first
keys.Insert(0, lowerKey + keySuffix);
}
foreach (var k in keys)
{
if (guideFormats.TryGetValue(k, out var format))
{
return format;
}
}
var lookup = new NamingFormatLookup();
var strategy = configPhaseStrategyFactory[config.ServiceType];
var dto = await strategy.ProcessNaming(config, guide, lookup);
_errors.Add(new InvalidNamingConfig(errorDescription, configFormatKey));
return null;
return new ProcessedNamingConfig {Dto = dto, InvalidNaming = lookup.Errors};
}
}

@ -0,0 +1,24 @@
using Autofac.Core.Registration;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Common;
using Recyclarr.Config.Models;
namespace Recyclarr.Cli.IntegrationTests.Pipelines;
[TestFixture]
[Parallelizable(ParallelScope.All)]
internal class MediaNamingConfigPhaseIntegrationTest : CliIntegrationFixture
{
private sealed record UnsupportedConfigType : ServiceConfiguration
{
public override SupportedServices ServiceType => (SupportedServices) 999;
}
[Test]
public async Task Throw_on_unknown_config_type()
{
var sut = Resolve<MediaNamingConfigPhase>();
var act = () => sut.Execute(new UnsupportedConfigType {InstanceName = ""});
await act.Should().ThrowAsync<ComponentNotRegisteredException>();
}
}

@ -0,0 +1,23 @@
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming.PipelinePhases.Config;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class NamingFormatLookupTest
{
[Test, AutoMockData]
public void Assign_null_when_config_null(
NamingFormatLookup sut)
{
var namingData = new Dictionary<string, string>
{
{"default", "folder_default"},
{"plex", "folder_plex"},
{"emby", "folder_emby"}
};
var result = sut.ObtainFormat(namingData, null, "");
result.Should().BeNull();
}
}

@ -0,0 +1,60 @@
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming.PipelinePhases.Config;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class RadarrMediaNamingConfigPhaseTest
{
private static readonly RadarrMediaNamingData RadarrNamingData = new()
{
Folder = new Dictionary<string, string>
{
{"default", "folder_default"},
{"plex", "folder_plex"},
{"emby", "folder_emby"}
},
File = new Dictionary<string, string>
{
{"default", "file_default"},
{"emby", "file_emby"},
{"jellyfin", "file_jellyfin"}
}
};
[Test, AutoMockData]
public async Task Radarr_naming(
[Frozen] IMediaNamingGuideService guide,
RadarrMediaNamingConfigPhase sut)
{
guide.GetRadarrNamingData().Returns(RadarrNamingData);
var config = new RadarrConfiguration
{
InstanceName = "radarr",
MediaNaming = new RadarrMediaNamingConfig
{
Folder = "plex",
Movie = new RadarrMovieNamingConfig
{
Rename = true,
Standard = "emby"
}
}
};
var result = await sut.ProcessNaming(config, guide, new NamingFormatLookup());
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new RadarrMediaNamingDto
{
RenameMovies = true,
StandardMovieFormat = "file_emby",
MovieFolderFormat = "folder_plex"
},
o => o.RespectingRuntimeTypes());
}
}

@ -0,0 +1,184 @@
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases.Config;
using Recyclarr.Compatibility.Sonarr;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming.PipelinePhases.Config;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class SonarrMediaNamingConfigPhaseTest
{
private static readonly SonarrMediaNamingData SonarrNamingData = new()
{
Season = new Dictionary<string, string>
{
{"default", "season_default"}
},
Series = new Dictionary<string, string>
{
{"default", "series_default"},
{"plex", "series_plex"},
{"emby", "series_emby"}
},
Episodes = new SonarrEpisodeNamingData
{
Standard = new Dictionary<string, string>
{
{"default:3", "episodes_standard_default_3"},
{"default:4", "episodes_standard_default_4"},
{"original", "episodes_standard_original"}
},
Daily = new Dictionary<string, string>
{
{"default:3", "episodes_daily_default_3"},
{"default:4", "episodes_daily_default_4"},
{"original", "episodes_daily_original"}
},
Anime = new Dictionary<string, string>
{
{"default:3", "episodes_anime_default_3"},
{"default:4", "episodes_anime_default_4"}
}
}
};
[Test, AutoMockData]
public async Task Sonarr_v3_naming(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
SonarrMediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = false
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "default",
Series = "plex",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "default",
Daily = "default",
Anime = "default"
}
}
};
var result = await sut.ProcessNaming(config, guide, new NamingFormatLookup());
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new SonarrMediaNamingDto
{
RenameEpisodes = true,
SeasonFolderFormat = "season_default",
SeriesFolderFormat = "series_plex",
StandardEpisodeFormat = "episodes_standard_default_3",
DailyEpisodeFormat = "episodes_daily_default_3",
AnimeEpisodeFormat = "episodes_anime_default_3"
});
}
[Test, AutoMockData]
public async Task Sonarr_v4_naming(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
SonarrMediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = true
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "default",
Series = "plex",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "default",
Daily = "default",
Anime = "default"
}
}
};
var result = await sut.ProcessNaming(config, guide, new NamingFormatLookup());
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new SonarrMediaNamingDto
{
RenameEpisodes = true,
SeasonFolderFormat = "season_default",
SeriesFolderFormat = "series_plex",
StandardEpisodeFormat = "episodes_standard_default_4",
DailyEpisodeFormat = "episodes_daily_default_4",
AnimeEpisodeFormat = "episodes_anime_default_4"
});
}
[Test, AutoMockData]
public async Task Sonarr_invalid_names(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
SonarrMediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = true
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "bad1",
Series = "bad2",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "bad3",
Daily = "bad4",
Anime = "bad5"
}
}
};
var lookup = new NamingFormatLookup();
var result = await sut.ProcessNaming(config, guide, lookup);
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new SonarrMediaNamingDto
{
RenameEpisodes = true
});
lookup.Errors.Should().BeEquivalentTo(new[]
{
new InvalidNamingConfig("Season Folder Format", "bad1"),
new InvalidNamingConfig("Series Folder Format", "bad2"),
new InvalidNamingConfig("Standard Episode Format", "bad3"),
new InvalidNamingConfig("Daily Episode Format", "bad4"),
new InvalidNamingConfig("Anime Episode Format", "bad5")
});
}
}

@ -1,281 +0,0 @@
using Recyclarr.Cli.Pipelines.MediaNaming.PipelinePhases;
using Recyclarr.Common;
using Recyclarr.Compatibility.Sonarr;
using Recyclarr.Config.Models;
using Recyclarr.ServarrApi.MediaNaming;
using Recyclarr.TrashGuide.MediaNaming;
namespace Recyclarr.Cli.Tests.Pipelines.MediaNaming;
[TestFixture]
public class MediaNamingConfigPhaseTest
{
private static readonly SonarrMediaNamingData SonarrNamingData = new()
{
Season = new Dictionary<string, string>
{
{"default", "season_default"}
},
Series = new Dictionary<string, string>
{
{"default", "series_default"},
{"plex", "series_plex"},
{"emby", "series_emby"}
},
Episodes = new SonarrEpisodeNamingData
{
Standard = new Dictionary<string, string>
{
{"default:3", "episodes_standard_default_3"},
{"default:4", "episodes_standard_default_4"},
{"original", "episodes_standard_original"}
},
Daily = new Dictionary<string, string>
{
{"default:3", "episodes_daily_default_3"},
{"default:4", "episodes_daily_default_4"},
{"original", "episodes_daily_original"}
},
Anime = new Dictionary<string, string>
{
{"default:3", "episodes_anime_default_3"},
{"default:4", "episodes_anime_default_4"}
}
}
};
private static readonly RadarrMediaNamingData RadarrNamingData = new()
{
Folder = new Dictionary<string, string>
{
{"default", "folder_default"},
{"plex", "folder_plex"},
{"emby", "folder_emby"}
},
File = new Dictionary<string, string>
{
{"default", "file_default"},
{"emby", "file_emby"},
{"jellyfin", "file_jellyfin"}
}
};
[Test, AutoMockData]
public async Task Sonarr_v3_naming(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
MediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = false
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "default",
Series = "plex",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "default",
Daily = "default",
Anime = "default"
}
}
};
var result = await sut.Execute(config);
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new ProcessedNamingConfig
{
Dto = new SonarrMediaNamingDto
{
RenameEpisodes = true,
SeasonFolderFormat = "season_default",
SeriesFolderFormat = "series_plex",
StandardEpisodeFormat = "episodes_standard_default_3",
DailyEpisodeFormat = "episodes_daily_default_3",
AnimeEpisodeFormat = "episodes_anime_default_3"
}
},
o => o.RespectingRuntimeTypes());
}
[Test, AutoMockData]
public async Task Sonarr_v4_naming(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
MediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = true
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "default",
Series = "plex",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "default",
Daily = "default",
Anime = "default"
}
}
};
var result = await sut.Execute(config);
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new ProcessedNamingConfig
{
Dto = new SonarrMediaNamingDto
{
RenameEpisodes = true,
SeasonFolderFormat = "season_default",
SeriesFolderFormat = "series_plex",
StandardEpisodeFormat = "episodes_standard_default_4",
DailyEpisodeFormat = "episodes_daily_default_4",
AnimeEpisodeFormat = "episodes_anime_default_4"
}
},
o => o.RespectingRuntimeTypes());
}
[Test, AutoMockData]
public async Task Sonarr_invalid_names(
[Frozen] ISonarrCapabilityFetcher capabilities,
[Frozen] IMediaNamingGuideService guide,
MediaNamingConfigPhase sut)
{
capabilities.GetCapabilities(default!).ReturnsForAnyArgs(new SonarrCapabilities
{
SupportsCustomFormats = true
});
guide.GetSonarrNamingData().Returns(SonarrNamingData);
var config = new SonarrConfiguration
{
InstanceName = "sonarr",
MediaNaming = new SonarrMediaNamingConfig
{
Season = "bad1",
Series = "bad2",
Episodes = new SonarrEpisodeNamingConfig
{
Rename = true,
Standard = "bad3",
Daily = "bad4",
Anime = "bad5"
}
}
};
var result = await sut.Execute(config);
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new ProcessedNamingConfig
{
Dto = new SonarrMediaNamingDto
{
RenameEpisodes = true
},
InvalidNaming = new[]
{
new InvalidNamingConfig("Season Folder Format", "bad1"),
new InvalidNamingConfig("Series Folder Format", "bad2"),
new InvalidNamingConfig("Standard Episode Format", "bad3"),
new InvalidNamingConfig("Daily Episode Format", "bad4"),
new InvalidNamingConfig("Anime Episode Format", "bad5")
}
},
o => o.RespectingRuntimeTypes());
}
[Test, AutoMockData]
public async Task Radarr_naming(
[Frozen] IMediaNamingGuideService guide,
MediaNamingConfigPhase sut)
{
guide.GetRadarrNamingData().Returns(RadarrNamingData);
var config = new RadarrConfiguration
{
InstanceName = "radarr",
MediaNaming = new RadarrMediaNamingConfig
{
Folder = "plex",
Movie = new RadarrMovieNamingConfig
{
Rename = true,
Standard = "emby"
}
}
};
var result = await sut.Execute(config);
result.Should().NotBeNull();
result.Should().BeEquivalentTo(new ProcessedNamingConfig
{
Dto = new RadarrMediaNamingDto
{
RenameMovies = true,
StandardMovieFormat = "file_emby",
MovieFolderFormat = "folder_plex"
}
},
o => o.RespectingRuntimeTypes());
}
private sealed record UnsupportedConfigType : ServiceConfiguration
{
public override SupportedServices ServiceType => default!;
}
[Test, AutoMockData]
public async Task Throw_on_unknown_config_type(
MediaNamingConfigPhase sut)
{
var act = () => sut.Execute(new UnsupportedConfigType {InstanceName = ""});
await act.Should().ThrowAsync<ArgumentException>();
}
[Test, AutoMockData]
public async Task Assign_null_when_config_null(
[Frozen] IMediaNamingGuideService guide,
MediaNamingConfigPhase sut)
{
guide.GetRadarrNamingData().Returns(RadarrNamingData);
var config = new RadarrConfiguration
{
InstanceName = "",
MediaNaming = new RadarrMediaNamingConfig
{
Folder = null
}
};
var result = await sut.Execute(config);
result.Should().NotBeNull();
result.Dto.Should().BeOfType<RadarrMediaNamingDto>()
.Which.MovieFolderFormat.Should().BeNull();
}
}
Loading…
Cancel
Save