using System.Collections.Generic; using FluentAssertions; using FluentAssertions.Json; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NSubstitute; using NUnit.Framework; using TestLibrary.NSubstitute; using Trash.Radarr.CustomFormat.Api; using Trash.Radarr.CustomFormat.Models; using Trash.Radarr.CustomFormat.Models.Cache; using Trash.Radarr.CustomFormat.Processors.PersistenceSteps; using Trash.TestLibrary; namespace Trash.Tests.Radarr.CustomFormat.Processors.PersistenceSteps { [TestFixture] [Parallelizable(ParallelScope.All)] public class QualityProfileApiPersistenceStepTest { [Test] public void Do_not_invoke_api_if_no_scores_to_update() { const string radarrQualityProfileData = @"[{ 'name': 'profile1', 'formatItems': [{ 'format': 1, 'name': 'cf1', 'score': 1 }, { 'format': 2, 'name': 'cf2', 'score': 0 }, { 'format': 3, 'name': 'cf3', 'score': 3 } ], 'id': 1 }]"; var api = Substitute.For(); api.GetQualityProfiles().Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { { "profile1", CfTestUtils.NewMapping( new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) { CacheEntry = new TrashIdMapping("", "") {CustomFormatId = 4} }, 100)) } }; var processor = new QualityProfileApiPersistenceStep(); processor.Process(api, cfScores); api.DidNotReceive().UpdateQualityProfile(Arg.Any(), Arg.Any()); } [Test] public void Invalid_quality_profile_names_are_reported() { const string radarrQualityProfileData = @"[{'name': 'profile1'}]"; var api = Substitute.For(); api.GetQualityProfiles().Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { {"wrong_profile_name", CfTestUtils.NewMapping()} }; var processor = new QualityProfileApiPersistenceStep(); processor.Process(api, cfScores); processor.InvalidProfileNames.Should().Equal("wrong_profile_name"); processor.UpdatedScores.Should().BeEmpty(); } [Test] public void Reset_scores_for_unmatched_cfs_if_enabled() { const string radarrQualityProfileData = @"[{ 'name': 'profile1', 'formatItems': [{ 'format': 1, 'name': 'cf1', 'score': 1 }, { 'format': 2, 'name': 'cf2', 'score': 50 }, { 'format': 3, 'name': 'cf3', 'score': 3 } ], 'id': 1 }]"; var api = Substitute.For(); api.GetQualityProfiles().Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { { "profile1", CfTestUtils.NewMappingWithReset( new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) { CacheEntry = new TrashIdMapping("", "", 2) }, 100)) } }; var processor = new QualityProfileApiPersistenceStep(); processor.Process(api, cfScores); processor.InvalidProfileNames.Should().BeEmpty(); processor.UpdatedScores.Should() .ContainKey("profile1").WhichValue.Should() .BeEquivalentTo(new List { new("cf1", 0, FormatScoreUpdateReason.Reset), new("cf2", 100, FormatScoreUpdateReason.Updated), new("cf3", 0, FormatScoreUpdateReason.Reset) }); api.Received().UpdateQualityProfile( Verify.That(j => j["formatItems"].Children().Should().HaveCount(3)), Arg.Any()); } [Test] public void Scores_are_set_in_quality_profile() { const string radarrQualityProfileData = @"[{ 'name': 'profile1', 'upgradeAllowed': false, 'cutoff': 20, 'items': [{ 'quality': { 'id': 10, 'name': 'Raw-HD', 'source': 'tv', 'resolution': 1080, 'modifier': 'rawhd' }, 'items': [], 'allowed': false } ], 'minFormatScore': 0, 'cutoffFormatScore': 0, 'formatItems': [{ 'format': 4, 'name': '3D', 'score': 0 }, { 'format': 3, 'name': 'BR-DISK', 'score': 0 }, { 'format': 1, 'name': 'asdf2', 'score': 0 } ], 'language': { 'id': 1, 'name': 'English' }, 'id': 1 }]"; var api = Substitute.For(); api.GetQualityProfiles().Returns(JsonConvert.DeserializeObject>(radarrQualityProfileData)); var cfScores = new Dictionary { { "profile1", CfTestUtils.NewMapping( new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) { // First match by ID CacheEntry = new TrashIdMapping("", "", 4) }, 100), new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) { // Should NOT match because we do not use names to assign scores CacheEntry = new TrashIdMapping("", "BR-DISK") }, 101), new FormatMappingEntry(new ProcessedCustomFormatData("", "", new JObject()) { // Second match by ID CacheEntry = new TrashIdMapping("", "", 1) }, 102)) } }; var processor = new QualityProfileApiPersistenceStep(); processor.Process(api, cfScores); var expectedProfileJson = JObject.Parse(@"{ 'name': 'profile1', 'upgradeAllowed': false, 'cutoff': 20, 'items': [{ 'quality': { 'id': 10, 'name': 'Raw-HD', 'source': 'tv', 'resolution': 1080, 'modifier': 'rawhd' }, 'items': [], 'allowed': false } ], 'minFormatScore': 0, 'cutoffFormatScore': 0, 'formatItems': [{ 'format': 4, 'name': '3D', 'score': 100 }, { 'format': 3, 'name': 'BR-DISK', 'score': 0 }, { 'format': 1, 'name': 'asdf2', 'score': 102 } ], 'language': { 'id': 1, 'name': 'English' }, 'id': 1 }"); api.Received() .UpdateQualityProfile(Verify.That(a => a.Should().BeEquivalentTo(expectedProfileJson)), 1); processor.InvalidProfileNames.Should().BeEmpty(); processor.UpdatedScores.Should() .ContainKey("profile1").WhichValue.Should() .BeEquivalentTo(new List { new("3D", 100, FormatScoreUpdateReason.Updated), new("asdf2", 102, FormatScoreUpdateReason.Updated) }); } } }