fix: Clone/update config repo in config create command

pull/201/head
Robert Dailey 11 months ago
parent 01baaa2be5
commit 58927728f8

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Fixed
- Clone config template repo when `config create -t` is used.
## [5.1.0] - 2023-06-26 ## [5.1.0] - 2023-06-26
### Added ### Added

@ -2,8 +2,6 @@ using System.IO.Abstractions;
using System.Reflection; using System.Reflection;
using Autofac; using Autofac;
using Autofac.Extras.Ordering; using Autofac.Extras.Ordering;
using AutoMapper.Contrib.Autofac.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using FluentValidation; using FluentValidation;
using Recyclarr.Cli.Cache; using Recyclarr.Cli.Cache;
using Recyclarr.Cli.Console.Helpers; using Recyclarr.Cli.Console.Helpers;
@ -35,7 +33,7 @@ public static class CompositionRoot
RegisterLogger(builder); RegisterLogger(builder);
builder.RegisterModule<MigrationAutofacModule>(); builder.RegisterModule<MigrationAutofacModule>();
builder.RegisterModule<TrashLibAutofacModule>(); builder.RegisterModule(new TrashLibAutofacModule {AdditionalMapperProfileAssembly = thisAssembly});
builder.RegisterModule<ServiceProcessorsAutofacModule>(); builder.RegisterModule<ServiceProcessorsAutofacModule>();
builder.RegisterModule<CacheAutofacModule>(); builder.RegisterModule<CacheAutofacModule>();
@ -49,10 +47,6 @@ public static class CompositionRoot
builder.RegisterAssemblyTypes(thisAssembly) builder.RegisterAssemblyTypes(thisAssembly)
.AsClosedTypesOf(typeof(IValidator<>)) .AsClosedTypesOf(typeof(IValidator<>))
.As<IValidator>(); .As<IValidator>();
builder.RegisterAutoMapper(c => c.AddCollectionMappers(), false,
thisAssembly,
typeof(TrashLibAutofacModule).Assembly);
} }
private static void PipelineRegistrations(ContainerBuilder builder) private static void PipelineRegistrations(ContainerBuilder builder)

@ -1,6 +1,5 @@
using Autofac.Features.Indexed; using Autofac.Features.Indexed;
using Recyclarr.TrashLib.Config.Listers; using Recyclarr.TrashLib.Config.Listers;
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.Cli.Processors.Config; namespace Recyclarr.Cli.Processors.Config;
@ -8,25 +7,15 @@ public class ConfigListProcessor
{ {
private readonly ILogger _log; private readonly ILogger _log;
private readonly IIndex<ConfigCategory, IConfigLister> _configListers; private readonly IIndex<ConfigCategory, IConfigLister> _configListers;
private readonly IConfigTemplatesRepo _repo;
public ConfigListProcessor( public ConfigListProcessor(ILogger log, IIndex<ConfigCategory, IConfigLister> configListers)
ILogger log,
IIndex<ConfigCategory, IConfigLister> configListers,
IConfigTemplatesRepo repo)
{ {
_log = log; _log = log;
_configListers = configListers; _configListers = configListers;
_repo = repo;
} }
public async Task Process(ConfigCategory listCategory) public async Task Process(ConfigCategory listCategory)
{ {
if (listCategory == ConfigCategory.Templates)
{
await _repo.Update();
}
_log.Debug("Listing configuration for category {Category}", listCategory); _log.Debug("Listing configuration for category {Category}", listCategory);
if (!_configListers.TryGetValue(listCategory, out var lister)) if (!_configListers.TryGetValue(listCategory, out var lister))
{ {

@ -35,11 +35,11 @@ public class TemplateConfigCreator : IConfigCreator
} }
[SuppressMessage("Design", "CA1031:Do not catch general exception types")] [SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public Task Create(ICreateConfigSettings settings) public async Task Create(ICreateConfigSettings settings)
{ {
_log.Debug("Creating config from templates: {Templates}", settings.Templates); _log.Debug("Creating config from templates: {Templates}", settings.Templates);
var matchingTemplateData = _templates.LoadTemplateData() var matchingTemplateData = (await _templates.LoadTemplateData())
.IntersectBy(settings.Templates, path => path.Id, StringComparer.CurrentCultureIgnoreCase) .IntersectBy(settings.Templates, path => path.Id, StringComparer.CurrentCultureIgnoreCase)
.Select(x => x.TemplateFile); .Select(x => x.TemplateFile);
@ -94,7 +94,5 @@ public class TemplateConfigCreator : IConfigCreator
_log.Error(e, "Unable to save configuration template file"); _log.Error(e, "Unable to save configuration template file");
} }
} }
return Task.CompletedTask;
} }
} }

@ -8,8 +8,6 @@
<PackageReference Include="Autofac" /> <PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Extras.AggregateService" /> <PackageReference Include="Autofac.Extras.AggregateService" />
<PackageReference Include="Autofac.Extras.Ordering" /> <PackageReference Include="Autofac.Extras.Ordering" />
<PackageReference Include="AutoMapper.Collection" />
<PackageReference Include="AutoMapper.Contrib.Autofac.DependencyInjection" />
<PackageReference Include="JetBrains.Annotations" /> <PackageReference Include="JetBrains.Annotations" />
<PackageReference Include="Serilog" /> <PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Expressions" /> <PackageReference Include="Serilog.Expressions" />

@ -15,13 +15,11 @@ public class ConfigAutofacModule : Module
{ {
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
var thisAssembly = typeof(ConfigAutofacModule).Assembly; builder.RegisterAssemblyTypes(ThisAssembly)
builder.RegisterAssemblyTypes(thisAssembly)
.AsClosedTypesOf(typeof(IValidator<>)) .AsClosedTypesOf(typeof(IValidator<>))
.As<IValidator>(); .As<IValidator>();
builder.RegisterAssemblyTypes(thisAssembly) builder.RegisterAssemblyTypes(ThisAssembly)
.AssignableTo<IYamlBehavior>() .AssignableTo<IYamlBehavior>()
.As<IYamlBehavior>(); .As<IYamlBehavior>();

@ -16,9 +16,9 @@ public class ConfigTemplateLister : IConfigLister
_guideService = guideService; _guideService = guideService;
} }
public Task List() public async Task List()
{ {
var data = _guideService.LoadTemplateData(); var data = await _guideService.LoadTemplateData();
var table = new Table(); var table = new Table();
var empty = new Markup(""); var empty = new Markup("");
@ -34,7 +34,6 @@ public class ConfigTemplateLister : IConfigLister
} }
_console.Write(table); _console.Write(table);
return Task.CompletedTask;
} }
private static IEnumerable<Markup> RenderTemplates( private static IEnumerable<Markup> RenderTemplates(

@ -31,8 +31,10 @@ public class ConfigTemplateGuideService : IConfigTemplateGuideService
_repo = repo; _repo = repo;
} }
public IReadOnlyCollection<TemplatePath> LoadTemplateData() public async Task<IReadOnlyCollection<TemplatePath>> LoadTemplateData()
{ {
await _repo.Update();
var templatesPath = _repo.Path.File("templates.json"); var templatesPath = _repo.Path.File("templates.json");
if (!templatesPath.Exists) if (!templatesPath.Exists)
{ {

@ -2,5 +2,5 @@ namespace Recyclarr.TrashLib.Config.Services;
public interface IConfigTemplateGuideService public interface IConfigTemplateGuideService
{ {
IReadOnlyCollection<TemplatePath> LoadTemplateData(); Task<IReadOnlyCollection<TemplatePath>> LoadTemplateData();
} }

@ -4,6 +4,8 @@
<PackageReference Include="Autofac.Extras.AggregateService" /> <PackageReference Include="Autofac.Extras.AggregateService" />
<PackageReference Include="Autofac.Extras.Ordering" /> <PackageReference Include="Autofac.Extras.Ordering" />
<PackageReference Include="AutoMapper" /> <PackageReference Include="AutoMapper" />
<PackageReference Include="AutoMapper.Collection" />
<PackageReference Include="AutoMapper.Contrib.Autofac.DependencyInjection" />
<PackageReference Include="CliWrap" /> <PackageReference Include="CliWrap" />
<PackageReference Include="FluentValidation" /> <PackageReference Include="FluentValidation" />
<PackageReference Include="Flurl" /> <PackageReference Include="Flurl" />

@ -1,5 +1,8 @@
using System.Reflection;
using Autofac; using Autofac;
using Autofac.Extras.Ordering; using Autofac.Extras.Ordering;
using AutoMapper.Contrib.Autofac.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.Common.FluentValidation; using Recyclarr.Common.FluentValidation;
using Recyclarr.TrashLib.ApiServices; using Recyclarr.TrashLib.ApiServices;
@ -9,11 +12,14 @@ using Recyclarr.TrashLib.Http;
using Recyclarr.TrashLib.Repo; using Recyclarr.TrashLib.Repo;
using Recyclarr.TrashLib.Repo.VersionControl; using Recyclarr.TrashLib.Repo.VersionControl;
using Recyclarr.TrashLib.Startup; using Recyclarr.TrashLib.Startup;
using Module = Autofac.Module;
namespace Recyclarr.TrashLib; namespace Recyclarr.TrashLib;
public class TrashLibAutofacModule : Module public class TrashLibAutofacModule : Module
{ {
public Assembly? AdditionalMapperProfileAssembly { get; init; }
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
base.Load(builder); base.Load(builder);
@ -31,6 +37,14 @@ public class TrashLibAutofacModule : Module
builder.RegisterModule<ConfigAutofacModule>(); builder.RegisterModule<ConfigAutofacModule>();
builder.RegisterType<ServiceRequestBuilder>().As<IServiceRequestBuilder>(); builder.RegisterType<ServiceRequestBuilder>().As<IServiceRequestBuilder>();
builder.RegisterType<FlurlClientFactory>().As<IFlurlClientFactory>().SingleInstance(); builder.RegisterType<FlurlClientFactory>().As<IFlurlClientFactory>().SingleInstance();
var mapperAssemblies = new List<Assembly> {ThisAssembly};
if (AdditionalMapperProfileAssembly is not null)
{
mapperAssemblies.Add(AdditionalMapperProfileAssembly);
}
builder.RegisterAutoMapper(c => c.AddCollectionMappers(), false, mapperAssemblies.ToArray());
} }
private static void CommonRegistrations(ContainerBuilder builder) private static void CommonRegistrations(ContainerBuilder builder)

@ -8,6 +8,8 @@ public abstract class CliIntegrationFixture : TrashLibIntegrationFixture
{ {
protected override void RegisterTypes(ContainerBuilder builder) protected override void RegisterTypes(ContainerBuilder builder)
{ {
// Do NOT invoke the base method here!
// We are deliberately REPLACING those registrations (the composition root here is a SUPERSET).
CompositionRoot.Setup(builder); CompositionRoot.Setup(builder);
} }
} }

@ -0,0 +1,60 @@
using System.IO.Abstractions;
using System.IO.Abstractions.Extensions;
using Autofac;
using Recyclarr.Cli.Console.Commands;
using Recyclarr.Cli.TestLibrary;
using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.Config.Listers;
using Recyclarr.TrashLib.Repo;
namespace Recyclarr.Cli.Tests.Console.Commands;
[TestFixture]
[Parallelizable(ParallelScope.All)]
public class ConfigCommandsIntegrationTest : CliIntegrationFixture
{
protected override void RegisterTypes(ContainerBuilder builder)
{
base.RegisterTypes(builder);
builder.RegisterMockFor<IConfigTemplatesRepo>(x =>
{
x.Path.Returns(_ => Fs.CurrentDirectory());
});
}
[Test]
public async Task Repo_update_is_called_on_config_list()
{
var repo = Resolve<IConfigTemplatesRepo>();
// Create this to make ConfigTemplateGuideService happy. It tries to parse this file, but
// it won't exist because we don't operate with real Git objects (so a clone never happens).
Fs.AddFile(repo.Path.File("templates.json"), new MockFileData("{}"));
var sut = Resolve<ConfigListCommand>();
await sut.ExecuteAsync(default!, new ConfigListCommand.CliSettings
{
ListCategory = ConfigCategory.Templates
});
await repo.Received().Update();
}
[Test]
public async Task Repo_update_is_called_on_config_create()
{
var repo = Resolve<IConfigTemplatesRepo>();
// Create this to make ConfigTemplateGuideService happy. It tries to parse this file, but
// it won't exist because we don't operate with real Git objects (so a clone never happens).
Fs.AddFile(repo.Path.File("templates.json"), new MockFileData("{}"));
var sut = Resolve<ConfigCreateCommand>();
await sut.ExecuteAsync(default!, new ConfigCreateCommand.CliSettings
{
TemplatesOption = new[] {"some-template"}
});
await repo.Received().Update();
}
}

@ -91,7 +91,7 @@ public class TemplateConfigCreatorTest : CliIntegrationFixture
} }
[Test] [Test]
public void Template_id_matching_works() public async Task Template_id_matching_works()
{ {
const string templatesJson = @" const string templatesJson = @"
{ {
@ -131,7 +131,7 @@ public class TemplateConfigCreatorTest : CliIntegrationFixture
}); });
var sut = Resolve<TemplateConfigCreator>(); var sut = Resolve<TemplateConfigCreator>();
sut.Create(settings); await sut.Create(settings);
Fs.AllFiles.Should().Contain(new[] Fs.AllFiles.Should().Contain(new[]
{ {

@ -4,8 +4,4 @@
<ProjectReference Include="..\Recyclarr.TestLibrary\Recyclarr.TestLibrary.csproj" /> <ProjectReference Include="..\Recyclarr.TestLibrary\Recyclarr.TestLibrary.csproj" />
<ProjectReference Include="..\..\Recyclarr.TrashLib\Recyclarr.TrashLib.csproj" /> <ProjectReference Include="..\..\Recyclarr.TrashLib\Recyclarr.TrashLib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Collection" />
<PackageReference Include="AutoMapper.Contrib.Autofac.DependencyInjection" />
</ItemGroup>
</Project> </Project>

@ -2,8 +2,6 @@ using System.IO.Abstractions;
using System.IO.Abstractions.Extensions; using System.IO.Abstractions.Extensions;
using Autofac; using Autofac;
using Autofac.Features.ResolveAnything; using Autofac.Features.ResolveAnything;
using AutoMapper.Contrib.Autofac.DependencyInjection;
using AutoMapper.EquivalencyExpression;
using Recyclarr.Common; using Recyclarr.Common;
using Recyclarr.TestLibrary.Autofac; using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TrashLib.ApiServices.System; using Recyclarr.TrashLib.ApiServices.System;
@ -62,7 +60,6 @@ public abstract class TrashLibIntegrationFixture : IDisposable
// Normally, the CLI's composition root registers this (because we can only do it once, and it must include // Normally, the CLI's composition root registers this (because we can only do it once, and it must include
// dependent assemblies). The TrashLib assembly does have its own mapping profiles. We register those here, but // dependent assemblies). The TrashLib assembly does have its own mapping profiles. We register those here, but
// not in the TrashLibAutofacModule. // not in the TrashLibAutofacModule.
builder.RegisterAutoMapper(c => c.AddCollectionMappers(), false, typeof(TrashLibAutofacModule).Assembly);
} }
private static ILogger CreateLogger() private static ILogger CreateLogger()

@ -17,11 +17,11 @@ public class ConfigTemplateGuideServiceTest : TrashLibIntegrationFixture
{ {
var act = () => _ = sut.LoadTemplateData(); var act = () => _ = sut.LoadTemplateData();
act.Should().Throw<InvalidDataException>().WithMessage("Recyclarr*templates*"); act.Should().ThrowAsync<InvalidDataException>().WithMessage("Recyclarr*templates*");
} }
[Test] [Test]
public void Normal_behavior() public async Task Normal_behavior()
{ {
var repo = Resolve<IConfigTemplatesRepo>(); var repo = Resolve<IConfigTemplatesRepo>();
var templateDir = repo.Path; var templateDir = repo.Path;
@ -43,7 +43,7 @@ public class ConfigTemplateGuideServiceTest : TrashLibIntegrationFixture
var sut = Resolve<ConfigTemplateGuideService>(); var sut = Resolve<ConfigTemplateGuideService>();
var data = sut.LoadTemplateData(); var data = await sut.LoadTemplateData();
data.Should().BeEquivalentTo(expectedPaths, o => o.Excluding(x => x.TemplateFile)); data.Should().BeEquivalentTo(expectedPaths, o => o.Excluding(x => x.TemplateFile));
data.Select(x => x.TemplateFile.FullName) data.Select(x => x.TemplateFile.FullName)
.Should().BeEquivalentTo(expectedPaths.Select(x => x.TemplateFile.FullName)); .Should().BeEquivalentTo(expectedPaths.Select(x => x.TemplateFile.FullName));

Loading…
Cancel
Save