From 8cc02a9d9c271128800b069f6a7b8d87f0919d3f Mon Sep 17 00:00:00 2001
From: Taloth Saldono <Taloth@users.noreply.github.com>
Date: Sat, 27 May 2017 22:02:30 +0200
Subject: [PATCH] Fixed: Minimum seeding check causing exception when release
 was pushed via api instead of by indexer.

---
 .../TorrentSeedingSpecificationFixture.cs     | 111 ++++++++++++++++++
 .../NzbDrone.Core.Test.csproj                 |   1 +
 .../TorrentSeedingSpecification.cs            |  23 ++--
 3 files changed, 128 insertions(+), 7 deletions(-)
 create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs

diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs
new file mode 100644
index 000000000..56c4ac84f
--- /dev/null
+++ b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs
@@ -0,0 +1,111 @@
+using FizzWare.NBuilder;
+using FluentAssertions;
+using Moq;
+using NUnit.Framework;
+using NzbDrone.Core.Datastore;
+using NzbDrone.Core.DecisionEngine.Specifications.Search;
+using NzbDrone.Core.Indexers;
+using NzbDrone.Core.Indexers.TorrentRss;
+using NzbDrone.Core.IndexerSearch.Definitions;
+using NzbDrone.Core.Parser.Model;
+using NzbDrone.Core.Tv;
+using NzbDrone.Test.Common;
+
+namespace NzbDrone.Core.Test.DecisionEngineTests.Search
+{
+    [TestFixture]
+    public class TorrentSeedingSpecificationFixture : TestBase<TorrentSeedingSpecification>
+    {
+        private Series _series;
+        private RemoteEpisode _remoteEpisode;
+        private IndexerDefinition _indexerDefinition;
+
+        [SetUp]
+        public void Setup()
+        {
+            _series = Builder<Series>.CreateNew().With(s => s.Id = 1).Build();
+
+            _remoteEpisode = new RemoteEpisode
+            {
+                Series = _series,
+                Release = new TorrentInfo
+                {
+                    IndexerId = 1,
+                    Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp",
+                    Seeders = 0
+                }
+            };
+
+            _indexerDefinition = new IndexerDefinition
+            {
+                Settings = new TorrentRssIndexerSettings { MinimumSeeders = 5 }
+            };
+
+            Mocker.GetMock<IIndexerFactory>()
+                  .Setup(v => v.Get(1))
+                  .Returns(_indexerDefinition);
+
+        }
+
+        private void GivenReleaseSeeders(int? seeders)
+        {
+            (_remoteEpisode.Release as TorrentInfo).Seeders = seeders;
+        }
+
+        [Test]
+        public void should_return_true_if_not_torrent()
+        {
+            _remoteEpisode.Release = new ReleaseInfo
+            {
+                IndexerId = 1,
+                Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp"
+            };
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_if_indexer_not_specified()
+        {
+            _remoteEpisode.Release.IndexerId = 0;
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_if_indexer_no_longer_exists()
+        {
+            Mocker.GetMock<IIndexerFactory>()
+                  .Setup(v => v.Get(It.IsAny<int>()))
+                  .Callback<int>(i => { throw new ModelNotFoundException(typeof(IndexerDefinition), i); });
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
+        }
+
+        [Test]
+        public void should_return_true_if_seeds_unknown()
+        {
+            GivenReleaseSeeders(null);
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
+        }
+
+        [TestCase(5)]
+        [TestCase(6)]
+        public void should_return_true_if_seeds_above_or_equal_to_limit(int seeders)
+        {
+            GivenReleaseSeeders(seeders);
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
+        }
+
+        [TestCase(0)]
+        [TestCase(4)]
+        public void should_return_false_if_seeds_belove_limit(int seeders)
+        {
+            GivenReleaseSeeders(seeders);
+
+            Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
+        }
+    }
+}
diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 4bc77554d..1610759ce 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -161,6 +161,7 @@
     <Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\RssSync\DeletedEpisodeFileSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
+    <Compile Include="DecisionEngineTests\Search\TorrentSeedingSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\SameEpisodesSpecificationFixture.cs" />
     <Compile Include="DecisionEngineTests\RawDiskSpecificationFixture.cs" />
diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs
index f095170c4..227123f96 100644
--- a/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs
+++ b/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs
@@ -1,6 +1,5 @@
-using System.Linq;
-using NLog;
-using NzbDrone.Common.Reflection;
+using NLog;
+using NzbDrone.Core.Datastore;
 using NzbDrone.Core.Indexers;
 using NzbDrone.Core.IndexerSearch.Definitions;
 using NzbDrone.Core.Parser.Model;
@@ -9,10 +8,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
 {
     public class TorrentSeedingSpecification : IDecisionEngineSpecification
     {
-        private readonly IndexerFactory _indexerFactory;
+        private readonly IIndexerFactory _indexerFactory;
         private readonly Logger _logger;
 
-        public TorrentSeedingSpecification(IndexerFactory indexerFactory, Logger logger)
+        public TorrentSeedingSpecification(IIndexerFactory indexerFactory, Logger logger)
         {
             _indexerFactory = indexerFactory;
             _logger = logger;
@@ -26,12 +25,22 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
         {
             var torrentInfo = remoteEpisode.Release as TorrentInfo;
 
-            if (torrentInfo == null)
+            if (torrentInfo == null || torrentInfo.IndexerId == 0)
             {
                 return Decision.Accept();
             }
 
-            var indexer = _indexerFactory.Get(torrentInfo.IndexerId);
+            IndexerDefinition indexer;
+            try
+            {
+                indexer = _indexerFactory.Get(torrentInfo.IndexerId);
+            }
+            catch (ModelNotFoundException)
+            {
+                _logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
+                return Decision.Accept();
+            }
+
             var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
 
             if (torrentIndexerSettings != null)