From 9c8d3b679dd870548c040de4f7cb0448f8d2b9be Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sun, 15 Sep 2024 12:19:08 -0500 Subject: [PATCH] Add 'qualitydefinition/limits' endpoint to get size limitations (cherry picked from commit 24f03fc1e96eba215f96312c791cf167f10499c7) --- .gitignore | 9 +- src/NzbDrone.Api.Test/Radarr.Api.Test.csproj | 1 + .../QualityDefinitionResourceValidatorTest.cs | 198 ++++++++++++++++++ .../Qualities/QualityDefinitionLimits.cs | 7 + .../Qualities/QualityDefinitionController.cs | 23 +- .../QualityDefinitionLimitsResource.cs | 6 + .../Qualities/QualityDefinitionResource.cs | 11 - .../QualityDefinitionResourceValidator.cs | 31 +++ src/Radarr.Http/REST/RestController.cs | 12 +- 9 files changed, 272 insertions(+), 26 deletions(-) create mode 100644 src/NzbDrone.Api.Test/v3/Qualities/QualityDefinitionResourceValidatorTest.cs create mode 100644 src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs create mode 100644 src/Radarr.Api.V3/Qualities/QualityDefinitionLimitsResource.cs create mode 100644 src/Radarr.Api.V3/Qualities/QualityDefinitionResourceValidator.cs diff --git a/.gitignore b/.gitignore index e633126a6c..95a3b62a66 100644 --- a/.gitignore +++ b/.gitignore @@ -165,15 +165,12 @@ Thumbs.db /tools/Addins/* packages.config.md5sum - -# Common IntelliJ Platform excludes - -# Ignore Rider projects completely for now -.idea/ - # ignore node_modules symlink node_modules node_modules.nosync # API doc generation .config/ + +# Ignore Jetbrains IntelliJ Workspace Directories +.idea/ diff --git a/src/NzbDrone.Api.Test/Radarr.Api.Test.csproj b/src/NzbDrone.Api.Test/Radarr.Api.Test.csproj index 9bfecde2cb..d77de079c1 100644 --- a/src/NzbDrone.Api.Test/Radarr.Api.Test.csproj +++ b/src/NzbDrone.Api.Test/Radarr.Api.Test.csproj @@ -8,6 +8,7 @@ + diff --git a/src/NzbDrone.Api.Test/v3/Qualities/QualityDefinitionResourceValidatorTest.cs b/src/NzbDrone.Api.Test/v3/Qualities/QualityDefinitionResourceValidatorTest.cs new file mode 100644 index 0000000000..b8ae42d4a9 --- /dev/null +++ b/src/NzbDrone.Api.Test/v3/Qualities/QualityDefinitionResourceValidatorTest.cs @@ -0,0 +1,198 @@ +using FluentValidation.TestHelper; +using NUnit.Framework; +using NzbDrone.Core.Qualities; +using Radarr.Api.V3.Qualities; + +namespace NzbDrone.Api.Test.v3.Qualities; + +[Parallelizable(ParallelScope.All)] +public class QualityDefinitionResourceValidatorTests +{ + private readonly QualityDefinitionResourceValidator _validator = new (); + + [Test] + public void Validate_fails_when_min_size_is_below_min_limit() + { + var resource = new QualityDefinitionResource + { + MinSize = QualityDefinitionLimits.Min - 1, + PreferredSize = null, + MaxSize = null + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.MinSize) + .WithErrorCode("GreaterThanOrEqualTo"); + } + + [Test] + public void Validate_fails_when_min_size_is_above_preferred_size_and_below_limit() + { + var resource = new QualityDefinitionResource + { + MinSize = 10, + PreferredSize = 5, + MaxSize = null + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.MinSize) + .WithErrorCode("LessThanOrEqualTo"); + + result.ShouldHaveValidationErrorFor(r => r.PreferredSize) + .WithErrorCode("GreaterThanOrEqualTo"); + } + + [Test] + public void Validate_passes_when_min_size_is_within_limits() + { + var resource = new QualityDefinitionResource + { + MinSize = QualityDefinitionLimits.Min, + PreferredSize = null, + MaxSize = null + }; + + var result = _validator.TestValidate(resource); + + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + public void Validate_fails_when_max_size_is_below_preferred_size_and_above_limit() + { + var resource = new QualityDefinitionResource + { + MinSize = null, + PreferredSize = 10, + MaxSize = 5 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.MaxSize) + .WithErrorCode("GreaterThanOrEqualTo"); + + result.ShouldHaveValidationErrorFor(r => r.PreferredSize) + .WithErrorCode("LessThanOrEqualTo"); + } + + [Test] + public void Validate_fails_when_max_size_exceeds_max_limit() + { + var resource = new QualityDefinitionResource + { + MinSize = null, + PreferredSize = null, + MaxSize = QualityDefinitionLimits.Max + 1 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.MaxSize) + .WithErrorCode("LessThanOrEqualTo"); + } + + [Test] + public void Validate_passes_when_max_size_is_within_limits() + { + var resource = new QualityDefinitionResource + { + MinSize = null, + PreferredSize = null, + MaxSize = QualityDefinitionLimits.Max + }; + + var result = _validator.TestValidate(resource); + + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + public void Validate_fails_when_preferred_size_is_below_min_size_and_above_max_size() + { + var resource = new QualityDefinitionResource + { + MinSize = 10, + PreferredSize = 7, + MaxSize = 5 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.PreferredSize) + .WithErrorCode("GreaterThanOrEqualTo"); + + result.ShouldHaveValidationErrorFor(r => r.MaxSize) + .WithErrorCode("GreaterThanOrEqualTo"); + } + + [Test] + public void Validate_passes_when_preferred_size_is_null_and_other_sizes_are_valid() + { + var resource = new QualityDefinitionResource + { + MinSize = 5, + PreferredSize = null, + MaxSize = 10 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + public void Validate_passes_when_preferred_size_equals_limits() + { + var resource = new QualityDefinitionResource + { + MinSize = 5, + PreferredSize = 5, + MaxSize = 10 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldNotHaveAnyValidationErrors(); + } + + [Test] + public void Validate_fails_when_all_sizes_are_provided_and_invalid() + { + var resource = new QualityDefinitionResource + { + MinSize = 15, + PreferredSize = 10, + MaxSize = 5 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldHaveValidationErrorFor(r => r.MinSize) + .WithErrorCode("LessThanOrEqualTo"); + + result.ShouldHaveValidationErrorFor(r => r.MaxSize) + .WithErrorCode("GreaterThanOrEqualTo"); + + result.ShouldHaveValidationErrorFor(r => r.PreferredSize) + .WithErrorCode("GreaterThanOrEqualTo"); + } + + [Test] + public void Validate_passes_when_preferred_size_is_valid_within_limits() + { + var resource = new QualityDefinitionResource + { + MinSize = 5, + PreferredSize = 7, + MaxSize = 10 + }; + + var result = _validator.TestValidate(resource); + + result.ShouldNotHaveAnyValidationErrors(); + } +} diff --git a/src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs b/src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs new file mode 100644 index 0000000000..a019f1d99b --- /dev/null +++ b/src/NzbDrone.Core/Qualities/QualityDefinitionLimits.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Qualities; + +public static class QualityDefinitionLimits +{ + public const int Min = 0; + public const int Max = 2000; +} diff --git a/src/Radarr.Api.V3/Qualities/QualityDefinitionController.cs b/src/Radarr.Api.V3/Qualities/QualityDefinitionController.cs index 7e6cbeef4e..719f184dd7 100644 --- a/src/Radarr.Api.V3/Qualities/QualityDefinitionController.cs +++ b/src/Radarr.Api.V3/Qualities/QualityDefinitionController.cs @@ -12,14 +12,21 @@ namespace Radarr.Api.V3.Qualities { [V3ApiController] - public class QualityDefinitionController : RestControllerWithSignalR, IHandle + public class QualityDefinitionController : + RestControllerWithSignalR, + IHandle { private readonly IQualityDefinitionService _qualityDefinitionService; - public QualityDefinitionController(IQualityDefinitionService qualityDefinitionService, IBroadcastSignalRMessage signalRBroadcaster) + public QualityDefinitionController( + IQualityDefinitionService qualityDefinitionService, + IBroadcastSignalRMessage signalRBroadcaster) : base(signalRBroadcaster) { _qualityDefinitionService = qualityDefinitionService; + + SharedValidator.RuleFor(c => c) + .SetValidator(new QualityDefinitionResourceValidator()); } [RestPutById] @@ -45,9 +52,7 @@ public List GetAll() public object UpdateMany([FromBody] List resource) { // Read from request - var qualityDefinitions = resource - .ToModel() - .ToList(); + var qualityDefinitions = resource.ToModel().ToList(); _qualityDefinitionService.UpdateMany(qualityDefinitions); @@ -55,6 +60,14 @@ public object UpdateMany([FromBody] List resource) .ToResource()); } + [HttpGet("limits")] + public ActionResult GetLimits() + { + return Ok(new QualityDefinitionLimitsResource( + QualityDefinitionLimits.Min, + QualityDefinitionLimits.Max)); + } + [NonAction] public void Handle(CommandExecutedEvent message) { diff --git a/src/Radarr.Api.V3/Qualities/QualityDefinitionLimitsResource.cs b/src/Radarr.Api.V3/Qualities/QualityDefinitionLimitsResource.cs new file mode 100644 index 0000000000..9c62485c3d --- /dev/null +++ b/src/Radarr.Api.V3/Qualities/QualityDefinitionLimitsResource.cs @@ -0,0 +1,6 @@ +namespace Radarr.Api.V3.Qualities; + +// SA1313 still applies to records until https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3181 is available in a release build. +#pragma warning disable SA1313 +public record QualityDefinitionLimitsResource(int Min, int Max); +#pragma warning restore SA1313 diff --git a/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs b/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs index 1522fe5636..6a01c5a933 100644 --- a/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs +++ b/src/Radarr.Api.V3/Qualities/QualityDefinitionResource.cs @@ -8,11 +8,8 @@ namespace Radarr.Api.V3.Qualities public class QualityDefinitionResource : RestResource { public Quality Quality { get; set; } - public string Title { get; set; } - public int Weight { get; set; } - public double? MinSize { get; set; } public double? MaxSize { get; set; } public double? PreferredSize { get; set; } @@ -30,13 +27,9 @@ public static QualityDefinitionResource ToResource(this QualityDefinition model) return new QualityDefinitionResource { Id = model.Id, - Quality = model.Quality, - Title = model.Title, - Weight = model.Weight, - MinSize = model.MinSize, MaxSize = model.MaxSize, PreferredSize = model.PreferredSize @@ -53,13 +46,9 @@ public static QualityDefinition ToModel(this QualityDefinitionResource resource) return new QualityDefinition { Id = resource.Id, - Quality = resource.Quality, - Title = resource.Title, - Weight = resource.Weight, - MinSize = resource.MinSize, MaxSize = resource.MaxSize, PreferredSize = resource.PreferredSize diff --git a/src/Radarr.Api.V3/Qualities/QualityDefinitionResourceValidator.cs b/src/Radarr.Api.V3/Qualities/QualityDefinitionResourceValidator.cs new file mode 100644 index 0000000000..61305a1b6a --- /dev/null +++ b/src/Radarr.Api.V3/Qualities/QualityDefinitionResourceValidator.cs @@ -0,0 +1,31 @@ +using FluentValidation; +using NzbDrone.Core.Qualities; + +namespace Radarr.Api.V3.Qualities; + +public class QualityDefinitionResourceValidator : AbstractValidator +{ + public QualityDefinitionResourceValidator() + { + RuleFor(c => c.MinSize) + .GreaterThanOrEqualTo(QualityDefinitionLimits.Min) + .WithErrorCode("GreaterThanOrEqualTo") + .LessThanOrEqualTo(c => c.PreferredSize ?? QualityDefinitionLimits.Max) + .WithErrorCode("LessThanOrEqualTo") + .When(c => c.MinSize is not null); + + RuleFor(c => c.PreferredSize) + .GreaterThanOrEqualTo(c => c.MinSize ?? QualityDefinitionLimits.Min) + .WithErrorCode("GreaterThanOrEqualTo") + .LessThanOrEqualTo(c => c.MaxSize ?? QualityDefinitionLimits.Max) + .WithErrorCode("LessThanOrEqualTo") + .When(c => c.PreferredSize is not null); + + RuleFor(c => c.MaxSize) + .GreaterThanOrEqualTo(c => c.PreferredSize ?? QualityDefinitionLimits.Min) + .WithErrorCode("GreaterThanOrEqualTo") + .LessThanOrEqualTo(QualityDefinitionLimits.Max) + .WithErrorCode("LessThanOrEqualTo") + .When(c => c.MaxSize is not null); + } +} diff --git a/src/Radarr.Http/REST/RestController.cs b/src/Radarr.Http/REST/RestController.cs index 109a054302..a87ed04049 100644 --- a/src/Radarr.Http/REST/RestController.cs +++ b/src/Radarr.Http/REST/RestController.cs @@ -69,11 +69,15 @@ public override void OnActionExecuting(ActionExecutingContext context) var skipValidate = skipAttribute?.Skip ?? false; var skipShared = skipAttribute?.SkipShared ?? false; - if (Request.Method == "POST" || Request.Method == "PUT") + if (Request.Method is "POST" or "PUT") { - var resourceArgs = context.ActionArguments.Values.Where(x => x.GetType() == typeof(TResource)) - .Select(x => x as TResource) - .ToList(); + var resourceArgs = context.ActionArguments.Values + .SelectMany(x => x switch + { + TResource single => new[] { single }, + IEnumerable multiple => multiple, + _ => Enumerable.Empty() + }); foreach (var resource in resourceArgs) {