parent
5c7cc8d829
commit
ec7516d6a6
@ -0,0 +1,53 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
public class CollectionJsonConverter : JsonConverterFactory
|
||||
{
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
return !IsExcludedType(typeToConvert) &&
|
||||
typeToConvert.IsGenericType &&
|
||||
IsAssignableToGenericType(typeToConvert, typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
private static bool IsExcludedType(Type type)
|
||||
{
|
||||
return type.IsPrimitive ||
|
||||
type == typeof(string);
|
||||
}
|
||||
|
||||
private static bool IsAssignableToGenericType(Type givenType, Type genericType)
|
||||
{
|
||||
var interfaceTypes = givenType.GetInterfaces()
|
||||
.Where(x => x.IsGenericType)
|
||||
.Select(x => x.GetGenericTypeDefinition());
|
||||
|
||||
return interfaceTypes.Contains(genericType);
|
||||
}
|
||||
|
||||
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var elementType = typeToConvert.GetGenericArguments()[0];
|
||||
Type converterType;
|
||||
|
||||
if (typeToConvert.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>))
|
||||
{
|
||||
converterType = typeof(ReadOnlyCollectionJsonConverter<>).MakeGenericType(elementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
var instance = Activator.CreateInstance(converterType);
|
||||
|
||||
if (instance is null)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
return (JsonConverter) instance;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using JorgeSerrano.Json;
|
||||
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
public static class GlobalJsonSerializerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON settings used for starr service API payloads.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions Services { get; } = new()
|
||||
{
|
||||
// This makes sure that null properties, such as maxSize and preferredSize in Radarr
|
||||
// Quality Definitions, do not get written out to JSON request bodies.
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver
|
||||
{
|
||||
Modifiers = {JsonSerializationModifiers.IgnoreNoSerializeAttribute}
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// JSON settings used by cache and other Recyclarr-owned JSON files.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions Recyclarr { get; } = new()
|
||||
{
|
||||
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy(),
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// JSON settings used by Trash Guides JSON files.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions Guide { get; } = new()
|
||||
{
|
||||
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy(),
|
||||
NumberHandling = JsonNumberHandling.AllowReadingFromString
|
||||
};
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
global using Serilog;
|
||||
global using JetBrains.Annotations;
|
@ -1,14 +1,15 @@
|
||||
using System.Text.Json;
|
||||
using Autofac;
|
||||
using Newtonsoft.Json;
|
||||
using Recyclarr.Json.Loading;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
public class JsonAutofacModule : Module
|
||||
{
|
||||
protected override void Load(ContainerBuilder builder)
|
||||
{
|
||||
base.Load(builder);
|
||||
builder.Register<Func<JsonSerializerSettings, IBulkJsonLoader>>(c =>
|
||||
builder.Register<Func<JsonSerializerOptions, IBulkJsonLoader>>(c =>
|
||||
{
|
||||
return settings => new BulkJsonLoader(c.Resolve<ILogger>(), settings);
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
public static class JsonExtensions
|
||||
{
|
||||
public static JsonSerializerOptions CopyOptionsWithout<T>(this JsonSerializerOptions options)
|
||||
where T : JsonConverter
|
||||
{
|
||||
var jsonSerializerOptions = new JsonSerializerOptions(options);
|
||||
|
||||
var jsonConverter = jsonSerializerOptions.Converters.FirstOrDefault(x => x.GetType() == typeof(T));
|
||||
if (jsonConverter is not null)
|
||||
{
|
||||
jsonSerializerOptions.Converters.Remove(jsonConverter);
|
||||
}
|
||||
|
||||
return jsonSerializerOptions;
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class JsonNoSerializeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
public static class JsonSerializationModifiers
|
||||
{
|
||||
private static bool HasAttribute<T>(JsonPropertyInfo? prop, IReadOnlyDictionary<string, IEnumerable<Type>> allAttrs)
|
||||
where T : Attribute
|
||||
{
|
||||
if (prop is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allAttrs.TryGetValue(prop.Name, out var attrs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return attrs.Any(x => x.IsAssignableTo(typeof(T)));
|
||||
}
|
||||
|
||||
public static void IgnoreNoSerializeAttribute(JsonTypeInfo type)
|
||||
{
|
||||
var attrs = type.Properties
|
||||
.Select(x => (Property: x, Attributes: x.PropertyType.GetCustomAttributes(false).Select(y => y.GetType())))
|
||||
.Where(x => x.Attributes.Any())
|
||||
.ToDictionary(x => x.Property.Name, x => x.Attributes);
|
||||
|
||||
var props = type.Properties;
|
||||
foreach (var prop in props)
|
||||
{
|
||||
prop.ShouldSerialize = (_, _) => !HasAttribute<JsonNoSerializeAttribute>(prop, attrs);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.Common.Extensions;
|
||||
|
||||
namespace Recyclarr.Common;
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
public static class JsonUtils
|
||||
{
|
@ -1,13 +1,13 @@
|
||||
using System.IO.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
namespace Recyclarr.Json.Loading;
|
||||
|
||||
public class GuideJsonLoader : IBulkJsonLoader
|
||||
{
|
||||
private readonly IBulkJsonLoader _loader;
|
||||
|
||||
public GuideJsonLoader(Func<JsonSerializerSettings, IBulkJsonLoader> jsonLoaderFactory)
|
||||
public GuideJsonLoader(Func<JsonSerializerOptions, IBulkJsonLoader> jsonLoaderFactory)
|
||||
{
|
||||
_loader = jsonLoaderFactory(GlobalJsonSerializerSettings.Guide);
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
namespace Recyclarr.Json.Loading;
|
||||
|
||||
public interface IBulkJsonLoader
|
||||
{
|
@ -1,13 +1,13 @@
|
||||
using System.IO.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
namespace Recyclarr.Json.Loading;
|
||||
|
||||
public class ServiceJsonLoader : IBulkJsonLoader
|
||||
{
|
||||
private readonly IBulkJsonLoader _loader;
|
||||
|
||||
public ServiceJsonLoader(Func<JsonSerializerSettings, IBulkJsonLoader> jsonLoaderFactory)
|
||||
public ServiceJsonLoader(Func<JsonSerializerOptions, IBulkJsonLoader> jsonLoaderFactory)
|
||||
{
|
||||
_loader = jsonLoaderFactory(GlobalJsonSerializerSettings.Services);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.Json;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class ReadOnlyCollectionJsonConverter<TElement> : JsonConverter<IReadOnlyCollection<TElement>>
|
||||
{
|
||||
public override IReadOnlyCollection<TElement> Read(
|
||||
ref Utf8JsonReader reader,
|
||||
Type typeToConvert,
|
||||
JsonSerializerOptions options)
|
||||
{
|
||||
if (reader.TokenType != JsonTokenType.StartArray)
|
||||
{
|
||||
throw new JsonException();
|
||||
}
|
||||
|
||||
var list = new List<TElement>();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var elementValue = JsonSerializer.Deserialize<TElement>(ref reader, options);
|
||||
if (elementValue is not null)
|
||||
{
|
||||
list.Add(elementValue);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public override void Write(
|
||||
Utf8JsonWriter writer,
|
||||
IReadOnlyCollection<TElement> value,
|
||||
JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var element in value)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, element, options);
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofac" />
|
||||
<PackageReference Include="JetBrains.Annotations" />
|
||||
<PackageReference Include="JorgeSerrano.Json.JsonSnakeCaseNamingPolicy" />
|
||||
<PackageReference Include="Serilog" />
|
||||
<PackageReference Include="System.Text.Json" />
|
||||
<PackageReference Include="SystemTextJson.JsonDiffPatch" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Recyclarr.Common\Recyclarr.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,33 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Recyclarr.Json;
|
||||
|
||||
namespace Recyclarr.TrashLib.Guide.ReleaseProfile;
|
||||
|
||||
internal class TermDataConverter : JsonConverter
|
||||
public class TermDataConverter : JsonConverter<TermData>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
public override TermData? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
// Not to be used for serialization
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object? ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
object? existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
var token = JToken.Load(reader);
|
||||
return token.Type switch
|
||||
if (reader.TokenType is JsonTokenType.String)
|
||||
{
|
||||
JTokenType.Object => token.ToObject<TermData>(),
|
||||
JTokenType.String => new TermData {Term = token.ToString()},
|
||||
_ => null
|
||||
};
|
||||
var str = reader.GetString();
|
||||
return str is not null ? new TermData {Term = str} : null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TermData>(ref reader, options.CopyOptionsWithout<TermDataConverter>());
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
public override void Write(Utf8JsonWriter writer, TermData value, JsonSerializerOptions options)
|
||||
{
|
||||
return objectType == typeof(TermData);
|
||||
JsonSerializer.Serialize(writer, value, options.CopyOptionsWithout<TermDataConverter>());
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
|
||||
public static class GlobalJsonSerializerSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON settings used for starr service API payloads.
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings Services { get; } = new()
|
||||
{
|
||||
// This makes sure that null properties, such as maxSize and preferredSize in Radarr
|
||||
// Quality Definitions, do not get written out to JSON request bodies.
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
ContractResolver = new ServiceContractResolver
|
||||
{
|
||||
NamingStrategy = new CamelCaseNamingStrategy()
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// JSON settings used by cache and other Recyclarr-owned JSON files.
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings Recyclarr { get; } = new()
|
||||
{
|
||||
Formatting = Formatting.Indented,
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new SnakeCaseNamingStrategy()
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// JSON settings used by Trash Guides JSON files.
|
||||
/// </summary>
|
||||
public static JsonSerializerSettings Guide => Recyclarr;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.TrashLib.Json;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class JsonNoSerializeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public sealed class JsonNoDeserializeAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
public class ServiceContractResolver : DefaultContractResolver
|
||||
{
|
||||
private static bool HasAttribute<T>(JsonProperty prop, Dictionary<string, IEnumerable<Type>> allAttrs)
|
||||
where T : Attribute
|
||||
{
|
||||
var name = prop.UnderlyingName;
|
||||
if (name is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allAttrs.TryGetValue(name, out var attrs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return attrs.Any(x => x.IsAssignableTo(typeof(T)));
|
||||
}
|
||||
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
var attrs = type.GetProperties()
|
||||
.Select(x => (Property: x, Attributes: x.GetCustomAttributes(false).Select(y => y.GetType())))
|
||||
.Where(x => x.Attributes.Any())
|
||||
.ToDictionary(x => x.Property.Name, x => x.Attributes);
|
||||
|
||||
var props = base.CreateProperties(type, memberSerialization);
|
||||
foreach (var prop in props)
|
||||
{
|
||||
prop.ShouldSerialize = _ => !HasAttribute<JsonNoSerializeAttribute>(prop, attrs);
|
||||
prop.ShouldDeserialize = _ => !HasAttribute<JsonNoDeserializeAttribute>(prop, attrs);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.TrashLib.Models;
|
||||
|
||||
public class FieldValueConverter : JsonConverter<object>
|
||||
{
|
||||
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.Number => reader.GetInt32(),
|
||||
JsonTokenType.String => reader.GetString(),
|
||||
_ => throw new JsonException($"CF field of type {reader.TokenType} is not supported")
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
@ -1,34 +1,29 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Recyclarr.TrashLib.Models;
|
||||
|
||||
public class FieldsArrayJsonConverter : JsonConverter
|
||||
public class FieldsArrayJsonConverter : JsonConverter<object>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||
public override bool CanConvert(Type typeToConvert)
|
||||
{
|
||||
serializer.Serialize(writer, value);
|
||||
return typeToConvert == typeof(CustomFormatFieldData) ||
|
||||
Array.Exists(typeToConvert.GetInterfaces(),
|
||||
x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
|
||||
}
|
||||
|
||||
public override object? ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
object? existingValue,
|
||||
JsonSerializer serializer)
|
||||
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var token = JToken.Load(reader);
|
||||
|
||||
// ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault
|
||||
return token.Type switch
|
||||
if (reader.TokenType is JsonTokenType.StartObject)
|
||||
{
|
||||
JTokenType.Object => new[] {token.ToObject<CustomFormatFieldData>()},
|
||||
JTokenType.Array => token.ToObject<CustomFormatFieldData[]>(),
|
||||
_ => throw new InvalidOperationException("Unsupported token type for CustomFormatFieldData")
|
||||
};
|
||||
return new[] {JsonSerializer.Deserialize<CustomFormatFieldData>(ref reader, options)!};
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<CustomFormatFieldData[]>(ref reader, options)!;
|
||||
}
|
||||
|
||||
public override bool CanConvert(Type objectType)
|
||||
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
|
||||
{
|
||||
return objectType.IsArray;
|
||||
JsonSerializer.Serialize(writer, value, options);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
using Autofac;
|
||||
using Recyclarr.TestLibrary;
|
||||
|
||||
namespace Recyclarr.Json.TestLibrary;
|
||||
|
||||
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
||||
public abstract class JsonIntegrationFixture : IntegrationTestFixture
|
||||
{
|
||||
protected override void RegisterTypes(ContainerBuilder builder)
|
||||
{
|
||||
base.RegisterTypes(builder);
|
||||
builder.RegisterModule<JsonAutofacModule>();
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Recyclarr.Json\Recyclarr.Json.csproj" />
|
||||
<ProjectReference Include="..\Recyclarr.TestLibrary\Recyclarr.TestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -1,7 +1,7 @@
|
||||
using System.IO.Abstractions;
|
||||
using Recyclarr.TestLibrary;
|
||||
|
||||
namespace Recyclarr.Common.Tests;
|
||||
namespace Recyclarr.Json.Tests;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
@ -0,0 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Recyclarr.Json\Recyclarr.Json.csproj" />
|
||||
<ProjectReference Include="..\Recyclarr.Json.TestLibrary\Recyclarr.Json.TestLibrary.csproj" />
|
||||
<ProjectReference Include="..\Recyclarr.TestLibrary\Recyclarr.TestLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,27 +0,0 @@
|
||||
using FluentAssertions.Equivalency;
|
||||
using FluentAssertions.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Recyclarr.TestLibrary.FluentAssertions;
|
||||
|
||||
public class JsonEquivalencyStep : IEquivalencyStep
|
||||
{
|
||||
public EquivalencyResult Handle(
|
||||
Comparands comparands,
|
||||
IEquivalencyValidationContext context,
|
||||
IEquivalencyValidator nestedValidator)
|
||||
{
|
||||
var canHandle = comparands.Subject?.GetType().IsAssignableTo(typeof(JToken)) ?? false;
|
||||
if (!canHandle)
|
||||
{
|
||||
return EquivalencyResult.ContinueWithNext;
|
||||
}
|
||||
|
||||
((JToken) comparands.Subject!).Should().BeEquivalentTo(
|
||||
(JToken) comparands.Expectation,
|
||||
context.Reason.FormattedMessage,
|
||||
context.Reason.Arguments);
|
||||
|
||||
return EquivalencyResult.AssertionCompleted;
|
||||
}
|
||||
}
|
@ -1,96 +1,41 @@
|
||||
using System.IO.Abstractions;
|
||||
using Autofac;
|
||||
using Autofac.Features.ResolveAnything;
|
||||
using Recyclarr.Common;
|
||||
using Recyclarr.TestLibrary;
|
||||
using Recyclarr.TestLibrary.Autofac;
|
||||
using Recyclarr.TrashLib.ApiServices.System;
|
||||
using Recyclarr.TrashLib.Repo.VersionControl;
|
||||
using Recyclarr.TrashLib.Startup;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Testing;
|
||||
|
||||
namespace Recyclarr.TrashLib.TestLibrary;
|
||||
|
||||
[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]
|
||||
public abstract class TrashLibIntegrationFixture : IDisposable
|
||||
public abstract class TrashLibIntegrationFixture : IntegrationTestFixture
|
||||
{
|
||||
protected IAppPaths Paths { get; }
|
||||
|
||||
protected TrashLibIntegrationFixture()
|
||||
{
|
||||
Fs = new MockFileSystem(new MockFileSystemOptions
|
||||
{
|
||||
CreateDefaultTempDir = false
|
||||
});
|
||||
|
||||
Paths = new AppPaths(Fs.CurrentDirectory().SubDirectory("test").SubDirectory("recyclarr"));
|
||||
|
||||
_container = new Lazy<IContainer>(() =>
|
||||
{
|
||||
var builder = new ContainerBuilder();
|
||||
|
||||
RegisterTypes(builder);
|
||||
|
||||
builder.RegisterInstance(Fs).As<IFileSystem>();
|
||||
builder.RegisterInstance(Paths).As<IAppPaths>();
|
||||
builder.RegisterInstance(Console).As<IAnsiConsole>();
|
||||
builder.RegisterInstance(Logger).As<ILogger>();
|
||||
|
||||
builder.RegisterMockFor<IGitRepository>();
|
||||
builder.RegisterMockFor<IGitRepositoryFactory>();
|
||||
builder.RegisterMockFor<IEnvironment>();
|
||||
builder.RegisterMockFor<IServiceInformation>(m =>
|
||||
{
|
||||
// By default, choose some extremely high number so that all the newest features are enabled.
|
||||
m.GetVersion(default!).ReturnsForAnyArgs(_ => new Version("99.0.0.0"));
|
||||
});
|
||||
|
||||
builder.RegisterSource<AnyConcreteTypeNotAlreadyRegisteredSource>();
|
||||
|
||||
return builder.Build();
|
||||
});
|
||||
}
|
||||
|
||||
// ReSharper disable once VirtualMemberNeverOverridden.Global
|
||||
// ReSharper disable once UnusedParameter.Global
|
||||
protected virtual void RegisterTypes(ContainerBuilder builder)
|
||||
protected override void RegisterTypes(ContainerBuilder builder)
|
||||
{
|
||||
base.RegisterTypes(builder);
|
||||
builder.RegisterModule<TrashLibAutofacModule>();
|
||||
}
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
private readonly Lazy<IContainer> _container;
|
||||
protected ILifetimeScope Container => _container.Value;
|
||||
|
||||
protected MockFileSystem Fs { get; }
|
||||
protected TestConsole Console { get; } = new();
|
||||
protected IAppPaths Paths { get; }
|
||||
protected TestableLogger Logger { get; } = new();
|
||||
|
||||
// ReSharper restore MemberCanBePrivate.Global
|
||||
|
||||
protected T Resolve<T>() where T : notnull
|
||||
protected override void RegisterStubsAndMocks(ContainerBuilder builder)
|
||||
{
|
||||
return Container.Resolve<T>();
|
||||
}
|
||||
base.RegisterStubsAndMocks(builder);
|
||||
|
||||
// ReSharper disable once VirtualMemberNeverOverridden.Global
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
builder.RegisterInstance(Paths).As<IAppPaths>();
|
||||
|
||||
if (_container.IsValueCreated)
|
||||
builder.RegisterMockFor<IGitRepository>();
|
||||
builder.RegisterMockFor<IGitRepositoryFactory>();
|
||||
builder.RegisterMockFor<IServiceInformation>(m =>
|
||||
{
|
||||
_container.Value.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
// By default, choose some extremely high number so that all the newest features are enabled.
|
||||
m.GetVersion(default!).ReturnsForAnyArgs(_ => new Version("99.0.0.0"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in new issue