diff --git a/frontend/src/System/Status/Health/Health.js b/frontend/src/System/Status/Health/Health.js index 0344b2e67..f55bc2b3d 100644 --- a/frontend/src/System/Status/Health/Health.js +++ b/frontend/src/System/Status/Health/Health.js @@ -18,6 +18,7 @@ function getInternalLink(source) { case 'IndexerRssCheck': case 'IndexerSearchCheck': case 'IndexerStatusCheck': + case 'IndexerJackettAllCheck': case 'IndexerLongTermStatusCheck': return ( + { + private List _indexers = new List(); + private IndexerDefinition _definition; + + [SetUp] + public void SetUp() + { + Mocker.GetMock() + .Setup(v => v.All()) + .Returns(_indexers); + + } + + private void GivenIndexer(string baseUrl, string apiPath) + { + var torznabSettings = new TorznabSettings + { + BaseUrl = baseUrl, + ApiPath = apiPath + }; + + _definition = new IndexerDefinition + { + Name = "Indexer", + ConfigContract = "TorznabSettings", + Settings = torznabSettings + }; + + _indexers.Add(_definition); + } + + [Test] + public void should_not_return_error_when_no_indexers() + { + Subject.Check().ShouldBeOk(); + } + + [TestCase("http://localhost:9117/", "api")] + public void should_not_return_error_when_no_jackett_all_indexers(string baseUrl, string apiPath) + { + GivenIndexer(baseUrl, apiPath); + + Subject.Check().ShouldBeOk(); + } + + [TestCase("http://localhost:9117/torznab/all/api", "api")] + [TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab", "api")] + [TestCase("http://localhost:9117/", "/torznab/all/api")] + [TestCase("http://localhost:9117/", "/api/v2.0/indexers/all/results/torznab")] + public void should_return_warning_if_any_jackett_all_indexer_exists(string baseUrl, string apiPath) + { + GivenIndexer(baseUrl, apiPath); + + Subject.Check().ShouldBeWarning(); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs index e2d5a9ea7..056d61b89 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; @@ -9,6 +10,7 @@ using NzbDrone.Core.Indexers.Newznab; using NzbDrone.Core.Indexers.Torznab; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Validation; namespace NzbDrone.Core.Test.IndexerTests.TorznabTests { @@ -30,7 +32,11 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests } }; - _caps = new NewznabCapabilities(); + _caps = new NewznabCapabilities + { + Categories = Builder.CreateListOfSize(1).All().With(t => t.Id = 1).Build().ToList() + }; + Mocker.GetMock() .Setup(v => v.GetCapabilities(It.IsAny())) .Returns(_caps); @@ -136,5 +142,50 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests Subject.PageSize.Should().Be(25); } + + [TestCase("http://localhost:9117/", "/api")] + public void url_and_api_not_jackett_all(string baseUrl, string apiPath) + { + var setting = new TorznabSettings() + { + BaseUrl = baseUrl, + ApiPath = apiPath + }; + + setting.Validate().IsValid.Should().BeTrue(); + } + + [TestCase("http://localhost:9117/torznab/all/api")] + [TestCase("http://localhost:9117/api/v2.0/indexers/all/results/torznab")] + public void jackett_all_url_should_not_validate(string baseUrl) + { + var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); + (Subject.Definition.Settings as TorznabSettings).BaseUrl = baseUrl; + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + var result = new NzbDroneValidationResult(Subject.Test()); + result.IsValid.Should().BeTrue(); + result.HasWarnings.Should().BeTrue(); + } + + [TestCase("/torznab/all/api")] + [TestCase("/api/v2.0/indexers/all/results/torznab")] + public void jackett_all_api_should_not_validate(string apiPath) + { + var recentFeed = ReadAllText(@"Files/Indexers/Torznab/torznab_tpb.xml"); + + Mocker.GetMock() + .Setup(o => o.Execute(It.Is(v => v.Method == HttpMethod.GET))) + .Returns(r => new HttpResponse(r, new HttpHeader(), recentFeed)); + + (Subject.Definition.Settings as TorznabSettings).ApiPath = apiPath; + + var result = new NzbDroneValidationResult(Subject.Test()); + result.IsValid.Should().BeTrue(); + result.HasWarnings.Should().BeTrue(); + } } } diff --git a/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs b/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs new file mode 100644 index 000000000..e18d1addb --- /dev/null +++ b/src/NzbDrone.Core/HealthCheck/Checks/IndexerJackettAllCheck.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers.Torznab; +using NzbDrone.Core.ThingiProvider.Events; + +namespace NzbDrone.Core.HealthCheck.Checks +{ + [CheckOn(typeof(ProviderUpdatedEvent))] + [CheckOn(typeof(ProviderDeletedEvent))] + [CheckOn(typeof(ProviderStatusChangedEvent))] + public class IndexerJackettAllCheck : HealthCheckBase + { + private readonly IIndexerFactory _providerFactory; + + public IndexerJackettAllCheck(IIndexerFactory providerFactory) + { + _providerFactory = providerFactory; + } + + public override HealthCheck Check() + { + var jackettAllProviders = _providerFactory.All().Where( + i => i.ConfigContract.Equals("TorznabSettings") && + ((i.Settings as TorznabSettings).BaseUrl.Contains("/torznab/all/api") || + (i.Settings as TorznabSettings).BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab") || + (i.Settings as TorznabSettings).ApiPath.Contains("/torznab/all/api") || + (i.Settings as TorznabSettings).ApiPath.Contains("/api/v2.0/indexers/all/results/torznab"))); + + if (jackettAllProviders.Empty()) + { + return new HealthCheck(GetType()); + } + + return new HealthCheck(GetType(), + HealthCheckResult.Warning, + string.Format("Indexers using the unsupported Jackett 'all' endpoint: {0}", + string.Join(", ", jackettAllProviders.Select(i => i.Name))), + "#jackett-all-endpoint-used"); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs index 08f28314f..5741ba3f7 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs @@ -93,7 +93,12 @@ namespace NzbDrone.Core.Indexers.Torznab protected override void Test(List failures) { base.Test(failures); - if (failures.HasErrors()) return; + if (failures.HasErrors()) + { + return; + } + + failures.AddIfNotNull(JackettAll()); failures.AddIfNotNull(TestCapabilities()); } @@ -125,6 +130,23 @@ namespace NzbDrone.Core.Indexers.Torznab } } + protected virtual ValidationFailure JackettAll() + { + if (Settings.ApiPath.Contains("/torznab/all") || + Settings.ApiPath.Contains("/api/v2.0/indexers/all/results/torznab") || + Settings.BaseUrl.Contains("/torznab/all") || + Settings.BaseUrl.Contains("/api/v2.0/indexers/all/results/torznab")) + { + return new NzbDroneValidationFailure("ApiPath", "Jackett's all endpoint is not supported, please add indexers individually") + { + IsWarning = true, + DetailedDescription = "Jackett's all endpoint is not supported, please add indexers individually" + }; + } + + return null; + } + public override object RequestAction(string action, IDictionary query) { if (action == "newznabCategories")