refactor: Consolidate the two QualityItem types

Previously, quality item was split into a second part for only the
Preferred size settings, because at one point both Radarr and Sonarr
didn't support them the same. However, that has changed now so they are
now consolidated.
pull/303/head
Robert Dailey 4 months ago
parent 8bbdec38f7
commit 0a8de83b34

@ -51,7 +51,7 @@ public class QualitySizeTransactionPhase(ILogger log) : ITransactionPipelinePhas
context.TransactionOutput = newQuality; context.TransactionOutput = newQuality;
} }
private static bool QualityIsDifferent(ServiceQualityDefinitionItem a, QualityItemWithPreferred b) private static bool QualityIsDifferent(ServiceQualityDefinitionItem a, QualityItem b)
{ {
return b.IsMinDifferent(a.MinSize) || b.IsMaxDifferent(a.MaxSize) || b.IsPreferredDifferent(a.PreferredSize); return b.IsMinDifferent(a.MinSize) || b.IsMaxDifferent(a.MaxSize) || b.IsPreferredDifferent(a.PreferredSize);
} }

@ -3,21 +3,25 @@ using System.Text;
namespace Recyclarr.TrashGuide.QualitySize; namespace Recyclarr.TrashGuide.QualitySize;
public class QualityItem(string quality, decimal min, decimal max) public class QualityItem(string quality, decimal min, decimal max, decimal preferred)
{ {
public const decimal MaxUnlimitedThreshold = 400; public const decimal MaxUnlimitedThreshold = 400;
public const decimal PreferredUnlimitedThreshold = 395;
public string Quality { get; } = quality; public string Quality { get; } = quality;
public decimal Min { get; } = min; public decimal Min { get; } = min;
public decimal Preferred { get; set; } = preferred;
public decimal Max { get; } = max; public decimal Max { get; } = max;
public decimal? MaxForApi => Max < MaxUnlimitedThreshold ? Max : null;
public decimal MinForApi => Min; public decimal MinForApi => Min;
public decimal? PreferredForApi => Preferred < PreferredUnlimitedThreshold ? Preferred : null;
public decimal? MaxForApi => Max < MaxUnlimitedThreshold ? Max : null;
public string AnnotatedMin => Min.ToString(CultureInfo.InvariantCulture); public string AnnotatedMin => Min.ToString(CultureInfo.InvariantCulture);
public string AnnotatedPreferred => AnnotatedValue(Preferred, PreferredUnlimitedThreshold);
public string AnnotatedMax => AnnotatedValue(Max, MaxUnlimitedThreshold); public string AnnotatedMax => AnnotatedValue(Max, MaxUnlimitedThreshold);
protected static string AnnotatedValue(decimal value, decimal threshold) private static string AnnotatedValue(decimal value, decimal threshold)
{ {
var builder = new StringBuilder(value.ToString(CultureInfo.InvariantCulture)); var builder = new StringBuilder(value.ToString(CultureInfo.InvariantCulture));
if (value >= threshold) if (value >= threshold)
@ -33,7 +37,17 @@ public class QualityItem(string quality, decimal min, decimal max)
return serviceValue != Min; return serviceValue != Min;
} }
protected static bool ValueWithThresholdIsDifferent(decimal? serviceValue, decimal guideValue, decimal threshold) public bool IsPreferredDifferent(decimal? serviceValue)
{
return ValueWithThresholdIsDifferent(serviceValue, Preferred, PreferredUnlimitedThreshold);
}
public bool IsMaxDifferent(decimal? serviceValue)
{
return ValueWithThresholdIsDifferent(serviceValue, Max, MaxUnlimitedThreshold);
}
private static bool ValueWithThresholdIsDifferent(decimal? serviceValue, decimal guideValue, decimal threshold)
{ {
return serviceValue == null return serviceValue == null
// If the service uses null, it's the same if the guide value == the max that null represents // If the service uses null, it's the same if the guide value == the max that null represents
@ -43,8 +57,9 @@ public class QualityItem(string quality, decimal min, decimal max)
: guideValue != serviceValue || guideValue == threshold; : guideValue != serviceValue || guideValue == threshold;
} }
public bool IsMaxDifferent(decimal? serviceValue) public decimal InterpolatedPreferred(decimal ratio)
{ {
return ValueWithThresholdIsDifferent(serviceValue, Max, MaxUnlimitedThreshold); var cappedMax = Math.Min(Max, PreferredUnlimitedThreshold);
return Math.Round(Min + (cappedMax - Min) * ratio, 1);
} }
} }

@ -1,22 +0,0 @@
namespace Recyclarr.TrashGuide.QualitySize;
public class QualityItemWithPreferred(string quality, decimal min, decimal max, decimal preferred)
: QualityItem(quality, min, max)
{
public const decimal PreferredUnlimitedThreshold = 395;
public decimal Preferred { get; set; } = preferred;
public decimal? PreferredForApi => Preferred < PreferredUnlimitedThreshold ? Preferred : null;
public string AnnotatedPreferred => AnnotatedValue(Preferred, PreferredUnlimitedThreshold);
public decimal InterpolatedPreferred(decimal ratio)
{
var cappedMax = Math.Min(Max, PreferredUnlimitedThreshold);
return Math.Round(Min + (cappedMax - Min) * ratio, 1);
}
public bool IsPreferredDifferent(decimal? serviceValue)
{
return ValueWithThresholdIsDifferent(serviceValue, Preferred, PreferredUnlimitedThreshold);
}
}

@ -3,6 +3,6 @@ namespace Recyclarr.TrashGuide.QualitySize;
public record QualitySizeData public record QualitySizeData
{ {
public string Type { get; init; } = ""; public string Type { get; init; } = "";
public IReadOnlyCollection<QualityItemWithPreferred> Qualities { get; init; } = public IReadOnlyCollection<QualityItem> Qualities { get; init; } =
Array.Empty<QualityItemWithPreferred>(); Array.Empty<QualityItem>();
} }

@ -90,7 +90,7 @@ public class QualitySizeConfigPhaseTest
Type = "real", Type = "real",
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("quality1", 0, 100, 90) new QualityItem("quality1", 0, 100, 90)
} }
} }
}); });
@ -102,7 +102,7 @@ public class QualitySizeConfigPhaseTest
context.ConfigOutput.Should().NotBeNull(); context.ConfigOutput.Should().NotBeNull();
context.ConfigOutput!.Qualities.Should().BeEquivalentTo(new[] context.ConfigOutput!.Qualities.Should().BeEquivalentTo(new[]
{ {
new QualityItemWithPreferred("quality1", 0, 100, 50) new QualityItem("quality1", 0, 100, 50)
}, },
o => o o => o
.Including(x => x.Quality) .Including(x => x.Quality)
@ -129,7 +129,7 @@ public class QualitySizeConfigPhaseTest
Type = "real", Type = "real",
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("quality1", 0, 100, 90) new QualityItem("quality1", 0, 100, 90)
} }
} }
}); });
@ -141,7 +141,7 @@ public class QualitySizeConfigPhaseTest
context.ConfigOutput.Should().NotBeNull(); context.ConfigOutput.Should().NotBeNull();
context.ConfigOutput!.Qualities.Should().BeEquivalentTo(new[] context.ConfigOutput!.Qualities.Should().BeEquivalentTo(new[]
{ {
new QualityItemWithPreferred("quality1", 0, 100, 90) new QualityItem("quality1", 0, 100, 90)
}, },
o => o o => o
.Including(x => x.Quality) .Including(x => x.Quality)

@ -18,8 +18,8 @@ public class QualitySizeTransactionPhaseTest
{ {
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("non_existent1", 0, 2, 1), new QualityItem("non_existent1", 0, 2, 1),
new QualityItemWithPreferred("non_existent2", 0, 2, 1) new QualityItem("non_existent2", 0, 2, 1)
} }
}, },
ApiFetchOutput = new List<ServiceQualityDefinitionItem> ApiFetchOutput = new List<ServiceQualityDefinitionItem>
@ -46,8 +46,8 @@ public class QualitySizeTransactionPhaseTest
{ {
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("same1", 0, 2, 1), new QualityItem("same1", 0, 2, 1),
new QualityItemWithPreferred("same2", 0, 2, 1) new QualityItem("same2", 0, 2, 1)
} }
}, },
ApiFetchOutput = new List<ServiceQualityDefinitionItem> ApiFetchOutput = new List<ServiceQualityDefinitionItem>
@ -84,8 +84,8 @@ public class QualitySizeTransactionPhaseTest
{ {
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("same1", 0, 2, 1), new QualityItem("same1", 0, 2, 1),
new QualityItemWithPreferred("different1", 0, 3, 1) new QualityItem("different1", 0, 3, 1)
} }
}, },
ApiFetchOutput = new List<ServiceQualityDefinitionItem> ApiFetchOutput = new List<ServiceQualityDefinitionItem>

@ -25,8 +25,8 @@ public class QualitySizeGuideParserTest : IntegrationTestFixture
Type = "series", Type = "series",
Qualities = new[] Qualities = new[]
{ {
new QualityItemWithPreferred("quality1", 1, 2, 3), new QualityItem("quality1", 1, 2, 3),
new QualityItemWithPreferred("quality2", 4.1m, 5.1m, 6.1m) new QualityItem("quality2", 4.1m, 5.1m, 6.1m)
} }
} }
}); });

@ -29,7 +29,7 @@ public class QualityItemTest
decimal? radarrValue, decimal? radarrValue,
bool isDifferent) bool isDifferent)
{ {
var data = new QualityItem("", 0, guideValue); var data = new QualityItem("", 0, guideValue, 0);
data.IsMaxDifferent(radarrValue) data.IsMaxDifferent(radarrValue)
.Should().Be(isDifferent); .Should().Be(isDifferent);
} }
@ -40,7 +40,7 @@ public class QualityItemTest
decimal radarrValue, decimal radarrValue,
bool isDifferent) bool isDifferent)
{ {
var data = new QualityItem("", guideValue, 0); var data = new QualityItem("", guideValue, 0, 0);
data.IsMinDifferent(radarrValue) data.IsMinDifferent(radarrValue)
.Should().Be(isDifferent); .Should().Be(isDifferent);
} }
@ -49,7 +49,7 @@ public class QualityItemTest
public void AnnotatedMax_OutsideThreshold_EqualsSameValueWithUnlimited() public void AnnotatedMax_OutsideThreshold_EqualsSameValueWithUnlimited()
{ {
const decimal testVal = QualityItem.MaxUnlimitedThreshold; const decimal testVal = QualityItem.MaxUnlimitedThreshold;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.AnnotatedMax.Should().Be($"{testVal} (Unlimited)"); data.AnnotatedMax.Should().Be($"{testVal} (Unlimited)");
} }
@ -57,7 +57,7 @@ public class QualityItemTest
public void AnnotatedMax_WithinThreshold_EqualsSameStringValue() public void AnnotatedMax_WithinThreshold_EqualsSameStringValue()
{ {
const decimal testVal = QualityItem.MaxUnlimitedThreshold - 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold - 1;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.AnnotatedMax.Should().Be($"{testVal}"); data.AnnotatedMax.Should().Be($"{testVal}");
} }
@ -65,7 +65,7 @@ public class QualityItemTest
public void AnnotatedMin_NoThreshold_EqualsSameValue() public void AnnotatedMin_NoThreshold_EqualsSameValue()
{ {
const decimal testVal = 10m; const decimal testVal = 10m;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.AnnotatedMax.Should().Be($"{testVal}"); data.AnnotatedMax.Should().Be($"{testVal}");
} }
@ -73,7 +73,7 @@ public class QualityItemTest
public void Max_AboveThreshold_EqualsSameValue() public void Max_AboveThreshold_EqualsSameValue()
{ {
const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.Max.Should().Be(testVal); data.Max.Should().Be(testVal);
} }
@ -81,7 +81,7 @@ public class QualityItemTest
public void MaxForApi_AboveThreshold_EqualsNull() public void MaxForApi_AboveThreshold_EqualsNull()
{ {
const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1; const decimal testVal = QualityItem.MaxUnlimitedThreshold + 1;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.MaxForApi.Should().Be(null); data.MaxForApi.Should().Be(null);
} }
@ -89,14 +89,131 @@ public class QualityItemTest
public void MaxForApi_HighestWithinThreshold_EqualsSameValue() public void MaxForApi_HighestWithinThreshold_EqualsSameValue()
{ {
const decimal testVal = QualityItem.MaxUnlimitedThreshold - 0.1m; const decimal testVal = QualityItem.MaxUnlimitedThreshold - 0.1m;
var data = new QualityItem("", 0, testVal); var data = new QualityItem("", 0, testVal, 0);
data.MaxForApi.Should().Be(testVal).And.Be(data.Max); data.MaxForApi.Should().Be(testVal).And.Be(data.Max);
} }
[Test] [Test]
public void MaxForApi_LowestWithinThreshold_EqualsSameValue() public void MaxForApi_LowestWithinThreshold_EqualsSameValue()
{ {
var data = new QualityItem("", 0, 0); var data = new QualityItem("", 0, 0, 0);
data.MaxForApi.Should().Be(0); data.MaxForApi.Should().Be(0);
} }
private static readonly object[] PreferredTestValues =
[
new object?[] {100m, 100m, false},
new object?[] {100m, 101m, true},
new object?[] {100m, 98m, true},
new object?[] {100m, null, true},
new object?[] {QualityItem.PreferredUnlimitedThreshold, null, false},
new object?[] {QualityItem.PreferredUnlimitedThreshold - 1, null, true},
new object?[]
{
QualityItem.PreferredUnlimitedThreshold, QualityItem.PreferredUnlimitedThreshold,
true
}
];
[TestCaseSource(nameof(PreferredTestValues))]
public void PreferredDifferent_WithVariousValues_ReturnsExpectedResult(
decimal guideValue,
decimal? radarrValue,
bool isDifferent)
{
var data = new QualityItem("", 0, 0, guideValue);
data.IsPreferredDifferent(radarrValue)
.Should().Be(isDifferent);
}
private static readonly object[] InterpolatedPreferredTestParams =
[
new[]
{
400m,
1.0m,
QualityItem.PreferredUnlimitedThreshold
},
new[]
{
QualityItem.PreferredUnlimitedThreshold,
1.0m,
QualityItem.PreferredUnlimitedThreshold
},
new[]
{
QualityItem.PreferredUnlimitedThreshold - 1m,
1.0m,
QualityItem.PreferredUnlimitedThreshold - 1m
},
new[]
{
10m,
0m,
0m
},
new[]
{
100m,
0.5m,
50m
}
];
[TestCaseSource(nameof(InterpolatedPreferredTestParams))]
public void InterpolatedPreferred_VariousValues_ExpectedResults(
decimal max,
decimal ratio,
decimal expectedResult)
{
var data = new QualityItem("", 0, max, 0);
data.InterpolatedPreferred(ratio).Should().Be(expectedResult);
}
[Test]
public void AnnotatedPreferred_OutsideThreshold_EqualsSameValueWithUnlimited()
{
const decimal testVal = QualityItem.PreferredUnlimitedThreshold;
var data = new QualityItem("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal} (Unlimited)");
}
[Test]
public void AnnotatedPreferred_WithinThreshold_EqualsSameStringValue()
{
const decimal testVal = QualityItem.PreferredUnlimitedThreshold - 1;
var data = new QualityItem("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal}");
}
[Test]
public void Preferred_AboveThreshold_EqualsSameValue()
{
const decimal testVal = QualityItem.PreferredUnlimitedThreshold + 1;
var data = new QualityItem("", 0, 0, testVal);
data.Preferred.Should().Be(testVal);
}
[Test]
public void PreferredForApi_AboveThreshold_EqualsNull()
{
const decimal testVal = QualityItem.PreferredUnlimitedThreshold + 1;
var data = new QualityItem("", 0, 0, testVal);
data.PreferredForApi.Should().Be(null);
}
[Test]
public void PreferredForApi_HighestWithinThreshold_EqualsSameValue()
{
const decimal testVal = QualityItem.PreferredUnlimitedThreshold - 0.1m;
var data = new QualityItem("", 0, 0, testVal);
data.PreferredForApi.Should().Be(testVal).And.Be(data.Preferred);
}
[Test]
public void PreferredForApi_LowestWithinThreshold_EqualsSameValue()
{
var data = new QualityItem("", 0, 0, 0);
data.PreferredForApi.Should().Be(0);
}
} }

@ -1,124 +0,0 @@
using Recyclarr.TrashGuide.QualitySize;
namespace Recyclarr.Tests.TrashGuide.QualitySize;
[TestFixture]
public class QualityItemWithPreferredTest
{
private static readonly object[] PreferredTestValues =
[
new object?[] {100m, 100m, false},
new object?[] {100m, 101m, true},
new object?[] {100m, 98m, true},
new object?[] {100m, null, true},
new object?[] {QualityItemWithPreferred.PreferredUnlimitedThreshold, null, false},
new object?[] {QualityItemWithPreferred.PreferredUnlimitedThreshold - 1, null, true},
new object?[]
{
QualityItemWithPreferred.PreferredUnlimitedThreshold, QualityItemWithPreferred.PreferredUnlimitedThreshold,
true
}
];
[TestCaseSource(nameof(PreferredTestValues))]
public void PreferredDifferent_WithVariousValues_ReturnsExpectedResult(
decimal guideValue,
decimal? radarrValue,
bool isDifferent)
{
var data = new QualityItemWithPreferred("", 0, 0, guideValue);
data.IsPreferredDifferent(radarrValue)
.Should().Be(isDifferent);
}
private static readonly object[] InterpolatedPreferredTestParams =
[
new[]
{
400m,
1.0m,
QualityItemWithPreferred.PreferredUnlimitedThreshold
},
new[]
{
QualityItemWithPreferred.PreferredUnlimitedThreshold,
1.0m,
QualityItemWithPreferred.PreferredUnlimitedThreshold
},
new[]
{
QualityItemWithPreferred.PreferredUnlimitedThreshold - 1m,
1.0m,
QualityItemWithPreferred.PreferredUnlimitedThreshold - 1m
},
new[]
{
10m,
0m,
0m
},
new[]
{
100m,
0.5m,
50m
}
];
[TestCaseSource(nameof(InterpolatedPreferredTestParams))]
public void InterpolatedPreferred_VariousValues_ExpectedResults(
decimal max,
decimal ratio,
decimal expectedResult)
{
var data = new QualityItemWithPreferred("", 0, max, 0);
data.InterpolatedPreferred(ratio).Should().Be(expectedResult);
}
[Test]
public void AnnotatedPreferred_OutsideThreshold_EqualsSameValueWithUnlimited()
{
const decimal testVal = QualityItemWithPreferred.PreferredUnlimitedThreshold;
var data = new QualityItemWithPreferred("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal} (Unlimited)");
}
[Test]
public void AnnotatedPreferred_WithinThreshold_EqualsSameStringValue()
{
const decimal testVal = QualityItemWithPreferred.PreferredUnlimitedThreshold - 1;
var data = new QualityItemWithPreferred("", 0, 0, testVal);
data.AnnotatedPreferred.Should().Be($"{testVal}");
}
[Test]
public void Preferred_AboveThreshold_EqualsSameValue()
{
const decimal testVal = QualityItemWithPreferred.PreferredUnlimitedThreshold + 1;
var data = new QualityItemWithPreferred("", 0, 0, testVal);
data.Preferred.Should().Be(testVal);
}
[Test]
public void PreferredForApi_AboveThreshold_EqualsNull()
{
const decimal testVal = QualityItemWithPreferred.PreferredUnlimitedThreshold + 1;
var data = new QualityItemWithPreferred("", 0, 0, testVal);
data.PreferredForApi.Should().Be(null);
}
[Test]
public void PreferredForApi_HighestWithinThreshold_EqualsSameValue()
{
const decimal testVal = QualityItemWithPreferred.PreferredUnlimitedThreshold - 0.1m;
var data = new QualityItemWithPreferred("", 0, 0, testVal);
data.PreferredForApi.Should().Be(testVal).And.Be(data.Preferred);
}
[Test]
public void PreferredForApi_LowestWithinThreshold_EqualsSameValue()
{
var data = new QualityItemWithPreferred("", 0, 0, 0);
data.PreferredForApi.Should().Be(0);
}
}
Loading…
Cancel
Save