Robert Dailey 10 months ago
parent 082c6cd206
commit d52b9f854d

@ -82,6 +82,9 @@ internal static class Program
_log = _scope.Resolve<ILogger>();
_log.Debug("Recyclarr Version: {Version}", GitVersionInformation.InformationalVersion);
var paths = _scope.Resolve<IAppPaths>();
_log.Debug("App Data Dir: {AppData}", paths.AppDataDirectory);
_tasks = _scope.Resolve<IOrderedEnumerable<IBaseCommandSetupTask>>().ToArray();
_tasks.ForEach(x => x.OnStart());
}

@ -1,17 +1,15 @@
using System.IO.Abstractions;
using Serilog;
namespace Recyclarr.Platform;
public class DefaultAppDataSetup(ILogger log, IEnvironment env, IFileSystem fs)
// Do NOT inject ILogger here because it introduces a circular dependency. LoggerFactory depends on IAppPaths.
public class DefaultAppDataSetup(IEnvironment env, IFileSystem fs)
{
public IAppPaths CreateAppPaths(string? appDataDirectoryOverride = null)
{
var appDir = GetAppDataDirectory(appDataDirectoryOverride);
var paths = new AppPaths(fs.DirectoryInfo.New(appDir));
log.Debug("App Data Dir: {AppData}", paths.AppDataDirectory);
// Initialize other directories used throughout the application
// Do not initialize the repo directory here; the GitRepositoryFactory handles that later.
paths.CacheDirectory.Create();

@ -7,11 +7,6 @@ public class PlatformAutofacModule : Module
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
RegisterAppPaths(builder);
}
private static void RegisterAppPaths(ContainerBuilder builder)
{
builder.RegisterType<DefaultAppDataSetup>();
builder.RegisterType<DefaultEnvironment>().As<IEnvironment>();
builder.RegisterType<DefaultRuntimeInformation>().As<IRuntimeInformation>();

@ -7,4 +7,5 @@ public interface IRepoMetadataBuilder
RepoMetadata GetMetadata();
IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories);
IDirectoryInfo DocsDirectory { get; }
IFileInfo MetadataPath { get; }
}

@ -4,16 +4,13 @@ using Recyclarr.Json;
namespace Recyclarr.Repo;
public class TrashRepoMetadataBuilder : IRepoMetadataBuilder
public class TrashRepoMetadataBuilder(ITrashGuidesRepo repo) : IRepoMetadataBuilder
{
private readonly Lazy<RepoMetadata> _metadata;
private readonly IDirectoryInfo _repoPath;
private RepoMetadata? _metadata;
private readonly IDirectoryInfo _repoPath = repo.Path;
public TrashRepoMetadataBuilder(ITrashGuidesRepo repo)
{
_repoPath = repo.Path;
_metadata = new Lazy<RepoMetadata>(() => Deserialize(_repoPath.File("metadata.json")));
}
public IFileInfo MetadataPath => _repoPath.File("metadata.json");
public IDirectoryInfo DocsDirectory => _repoPath.SubDirectory("docs");
private static RepoMetadata Deserialize(IFileInfo jsonFile)
{
@ -33,10 +30,8 @@ public class TrashRepoMetadataBuilder : IRepoMetadataBuilder
return listOfDirectories.Select(x => _repoPath.SubDirectory(x)).ToList();
}
public IDirectoryInfo DocsDirectory => _repoPath.SubDirectory("docs");
public RepoMetadata GetMetadata()
{
return _metadata.Value;
return _metadata ??= Deserialize(MetadataPath);
}
}

@ -0,0 +1,105 @@
using System.IO.Abstractions;
using Autofac;
using Autofac.Features.ResolveAnything;
using Recyclarr.Compatibility;
using Recyclarr.Platform;
using Recyclarr.Repo;
using Recyclarr.TestLibrary;
using Recyclarr.TestLibrary.Autofac;
using Recyclarr.VersionControl;
using Spectre.Console;
using Spectre.Console.Testing;
namespace Recyclarr.Cli.IntegrationTests;
public abstract class IntegrationTestFixture2 : IDisposable
{
private readonly Lazy<ILifetimeScope> _container;
protected ILifetimeScope Container => _container.Value;
protected MockFileSystem Fs { get; }
protected TestConsole Console { get; } = new();
protected TestableLogger Logger { get; } = new();
protected IntegrationTestFixture2()
{
Fs = new MockFileSystem(new MockFileSystemOptions
{
CreateDefaultTempDir = false
});
// Use Lazy because we shouldn't invoke virtual methods at construction time
_container = new Lazy<ILifetimeScope>(() =>
{
var builder = new ContainerBuilder();
RegisterTypes(builder);
RegisterStubsAndMocks(builder);
builder.RegisterSource<AnyConcreteTypeNotAlreadyRegisteredSource>();
return builder.Build();
});
}
/// <summary>
/// Register "real" types (usually Module-derived classes from other projects). This call happens
/// before
/// RegisterStubsAndMocks().
/// </summary>
protected virtual void RegisterTypes(ContainerBuilder builder)
{
CompositionRoot.Setup(builder);
}
/// <summary>
/// Override registrations made in the RegisterTypes() method. This method is called after
/// RegisterTypes().
/// </summary>
protected virtual void RegisterStubsAndMocks(ContainerBuilder builder)
{
builder.RegisterType<StubEnvironment>().As<IEnvironment>();
builder.RegisterInstance(Fs).As<IFileSystem>().AsSelf();
builder.RegisterInstance(Console).As<IAnsiConsole>();
builder.RegisterInstance(Logger).As<ILogger>();
builder.RegisterMockFor<IGitRepository>();
builder.RegisterMockFor<IGitRepositoryFactory>();
builder.RegisterMockFor<IServiceInformation>(m =>
{
// By default, choose some extremely high number so that all the newest features are enabled.
m.GetVersion().ReturnsForAnyArgs(_ => new Version("99.0.0.0"));
});
builder.RegisterDecorator<StubTrashRepoMetadataBuilder, IRepoMetadataBuilder>();
}
[TearDown]
public void DumpLogsToConsole()
{
foreach (var line in Console.Lines)
{
System.Console.WriteLine(line);
}
}
protected T Resolve<T>() where T : notnull
{
return Container.Resolve<T>();
}
// ReSharper disable once VirtualMemberNeverOverridden.Global
protected virtual void Dispose(bool disposing)
{
if (!disposing || !_container.IsValueCreated)
{
return;
}
_container.Value.Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}

@ -0,0 +1,41 @@
using System.IO.Abstractions;
using Recyclarr.Common.Extensions;
using Recyclarr.Platform;
namespace Recyclarr.Cli.IntegrationTests;
public class StubEnvironment(IFileSystem fs) : IEnvironment
{
private readonly Dictionary<string, string> _env = new();
private IDirectoryInfo BuildSpecialFolderPath(Environment.SpecialFolder folder)
{
return fs.CurrentDirectory().SubDirectory("special_folder", folder.ToString());
}
public string GetFolderPath(Environment.SpecialFolder folder)
{
return BuildSpecialFolderPath(folder).FullName;
}
public string GetFolderPath(Environment.SpecialFolder folder, Environment.SpecialFolderOption folderOption)
{
var path = BuildSpecialFolderPath(folder);
if (folderOption == Environment.SpecialFolderOption.Create)
{
path.Create();
}
return path.FullName;
}
public void AddEnvironmentVariable(string variable, string value)
{
_env.Add(variable, value);
}
public string? GetEnvironmentVariable(string variable)
{
return _env.GetValueOrDefault(variable);
}
}

@ -0,0 +1,52 @@
using System.IO.Abstractions;
using Recyclarr.Repo;
using Recyclarr.TestLibrary;
namespace Recyclarr.Cli.IntegrationTests;
public class StubTrashRepoMetadataBuilder : IRepoMetadataBuilder
{
private readonly IRepoMetadataBuilder _metadata;
private const string MetadataJson =
"""
{
"json_paths": {
"radarr": {
"custom_formats": ["docs/json/radarr/cf"],
"qualities": ["docs/json/radarr/quality-size"],
"naming": ["docs/json/radarr/naming"]
},
"sonarr": {
"release_profiles": ["docs/json/sonarr/rp"],
"custom_formats": ["docs/json/sonarr/cf"],
"qualities": ["docs/json/sonarr/quality-size"],
"naming": ["docs/json/sonarr/naming"]
}
},
"recyclarr": {
"templates": "docs/recyclarr-configs"
}
}
""";
public StubTrashRepoMetadataBuilder(MockFileSystem fs, IRepoMetadataBuilder metadata)
{
_metadata = metadata;
fs.AddFile(MetadataPath, new MockFileData(MetadataJson));
fs.AddSameFileFromEmbeddedResource(
DocsDirectory.SubDirectory("Radarr").File("Radarr-collection-of-custom-formats.md"),
typeof(StubTrashRepoMetadataBuilder));
fs.AddSameFileFromEmbeddedResource(
DocsDirectory.SubDirectory("Sonarr").File("sonarr-collection-of-custom-formats.md"),
typeof(StubTrashRepoMetadataBuilder));
}
public IDirectoryInfo DocsDirectory => _metadata.DocsDirectory;
public IFileInfo MetadataPath => _metadata.MetadataPath;
public RepoMetadata GetMetadata() => _metadata.GetMetadata();
public IReadOnlyList<IDirectoryInfo> ToDirectoryInfoList(IEnumerable<string> listOfDirectories)
=> _metadata.ToDirectoryInfoList(listOfDirectories);
}

@ -1,6 +1,6 @@
using AutoMapper;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class AutoMapperConfigurationTest : CliIntegrationFixture

@ -2,7 +2,7 @@ using System.IO.Abstractions;
using Recyclarr.Cli.Console.Setup;
using Recyclarr.Settings;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class BaseCommandSetupIntegrationTest : CliIntegrationFixture

@ -8,7 +8,7 @@ using Recyclarr.TestLibrary.Autofac;
using Serilog.Core;
using Spectre.Console;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
public class CompositionRootTest

@ -4,7 +4,7 @@ using Recyclarr.Cli.Console.Settings;
using Recyclarr.Cli.Processors.Config;
using Recyclarr.Repo;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class ConfigCreationProcessorIntegrationTest : CliIntegrationFixture

@ -1,7 +1,7 @@
using System.IO.Abstractions;
using Recyclarr.Cli.Processors.Config;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class ConfigManipulatorTest : CliIntegrationFixture

@ -6,7 +6,7 @@ using Recyclarr.Config;
using Recyclarr.Tests.TestLibrary;
using Recyclarr.TrashGuide.CustomFormat;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class CustomFormatTransactionPhaseTest : CliIntegrationFixture

@ -0,0 +1,55 @@
using System.IO.Abstractions;
using Autofac;
using Recyclarr.Cli.Console.Commands;
using Recyclarr.Common.Extensions;
using Recyclarr.Platform;
using Recyclarr.TestLibrary.Autofac;
using Recyclarr.TestLibrary.AutoFixture;
using Spectre.Console.Cli;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
public class PlatformIntegrationTest : IntegrationTestFixture2
{
protected override void RegisterStubsAndMocks(ContainerBuilder builder)
{
base.RegisterStubsAndMocks(builder);
builder.RegisterMockFor<IRuntimeInformation>();
}
[Test, AutoMockData]
public async Task Migrate_app_data_on_mac_os_dotnet8(CommandContext ctx)
{
// The "old" app data directory has to be in place before we create SyncCommand, since we want the
// automatic/transparent move of that directory to happen before the migration system checks it.
var env = Resolve<IEnvironment>();
var oldPath = new AppPaths(Fs.DirectoryInfo
.New(env.GetFolderPath(Environment.SpecialFolder.UserProfile))
.SubDirectory(".config", AppPaths.DefaultAppDataDirectoryName));
Fs.AddFile(oldPath.ConfigsDirectory.File("test.yml"), new MockFileData(
"""
radarr:
test_instance:
base_url: http://localhost:1000
api_key: key
"""));
// Fs.AddEmptyFile(oldPath.File("test.yml"));
// The runtime info mocking has to happen before the instantiation of SyncCommand.
var runtimeInfo = Resolve<IRuntimeInformation>();
runtimeInfo.IsPlatformOsx().Returns(true);
var cmd = Resolve<SyncCommand>();
var settings = new SyncCommand.CliSettings();
var newPath = Resolve<IAppPaths>();
var returnCode = await cmd.ExecuteAsync(ctx, settings);
returnCode.Should().Be(0);
Fs.AllFiles.Should().Contain(newPath.ConfigsDirectory.SubDirectory("configs").File("test.yml").FullName);
}
}

@ -1,7 +1,7 @@
using System.IO.Abstractions;
using Recyclarr.Settings;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class ServiceCompatibilityIntegrationTest : CliIntegrationFixture

@ -3,7 +3,7 @@ using Recyclarr.Cli.Console.Settings;
using Recyclarr.Cli.Processors.Config;
using Recyclarr.Repo;
namespace Recyclarr.Cli.IntegrationTests;
namespace Recyclarr.Cli.IntegrationTests.Tests;
[TestFixture]
internal class TemplateConfigCreatorIntegrationTest : CliIntegrationFixture

@ -8,7 +8,7 @@ namespace Recyclarr.TestLibrary;
public sealed class TestableLogger : ILogger
{
private readonly Logger _log;
private readonly List<string> _messages = new();
private readonly List<string> _messages = [];
public TestableLogger()
{

Loading…
Cancel
Save