using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Threading.Tasks; using FizzWare.NBuilder; using Moq; using NUnit.Framework; using NzbDrone.Common.Http; using NzbDrone.Core.Download; using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Exceptions; using NzbDrone.Core.Indexers; using NzbDrone.Core.Music; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; namespace NzbDrone.Core.Test.Download { [TestFixture] public class DownloadServiceFixture : CoreTest { private RemoteAlbum _parseResult; private List _downloadClients; [SetUp] public void Setup() { _downloadClients = new List(); Mocker.GetMock() .Setup(v => v.GetDownloadClients(It.IsAny())) .Returns(_downloadClients); Mocker.GetMock() .Setup(v => v.GetDownloadClient(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>())) .Returns>((v, i, f, t) => _downloadClients.FirstOrDefault(d => d.Protocol == v)); var episodes = Builder.CreateListOfSize(2) .TheFirst(1).With(s => s.Id = 12) .TheNext(1).With(s => s.Id = 99) .All().With(s => s.ArtistId = 5) .Build().ToList(); var releaseInfo = Builder.CreateNew() .With(v => v.DownloadProtocol = nameof(UsenetDownloadProtocol)) .With(v => v.DownloadUrl = "http://test.site/download1.ext") .Build(); _parseResult = Builder.CreateNew() .With(c => c.Artist = Builder.CreateNew().Build()) .With(c => c.Release = releaseInfo) .With(c => c.Albums = episodes) .Build(); } private Mock WithUsenetClient() { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition).Returns(Builder.CreateNew().Build()); _downloadClients.Add(mock.Object); mock.SetupGet(v => v.Protocol).Returns(nameof(UsenetDownloadProtocol)); return mock; } private Mock WithTorrentClient() { var mock = new Mock(MockBehavior.Default); mock.SetupGet(s => s.Definition).Returns(Builder.CreateNew().Build()); _downloadClients.Add(mock.Object); mock.SetupGet(v => v.Protocol).Returns(nameof(TorrentDownloadProtocol)); return mock; } [Test] public async Task Download_report_should_publish_on_grab_event() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())); await Subject.DownloadReport(_parseResult); VerifyEventPublished(); } [Test] public async Task Download_report_should_grab_using_client() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())); await Subject.DownloadReport(_parseResult); mock.Verify(s => s.Download(It.IsAny(), It.IsAny()), Times.Once()); } [Test] public void Download_report_should_not_publish_on_failed_grab_event() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Throws(new WebException()); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); VerifyEventNotPublished(); } [Test] public void Download_report_should_trigger_indexer_backoff_on_indexer_error() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Callback((v, indexer) => { throw new ReleaseDownloadException(v.Release, "Error", new WebException()); }); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock() .Verify(v => v.RecordFailure(It.IsAny(), It.IsAny()), Times.Once()); } [Test] public void Download_report_should_trigger_indexer_backoff_on_http429_with_long_time() { var request = new HttpRequest("http://my.indexer.com"); var response = new HttpResponse(request, new HttpHeader(), Array.Empty(), (HttpStatusCode)429); response.Headers["Retry-After"] = "300"; var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Callback((v, indexer) => { throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response)); }); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock() .Verify(v => v.RecordFailure(It.IsAny(), TimeSpan.FromMinutes(5.0)), Times.Once()); } [Test] public void Download_report_should_trigger_indexer_backoff_on_http429_based_on_date() { var request = new HttpRequest("http://my.indexer.com"); var response = new HttpResponse(request, new HttpHeader(), Array.Empty(), (HttpStatusCode)429); response.Headers["Retry-After"] = DateTime.UtcNow.AddSeconds(300).ToString("r"); var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Callback((v, indexer) => { throw new ReleaseDownloadException(v.Release, "Error", new TooManyRequestsException(request, response)); }); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock() .Verify(v => v.RecordFailure(It.IsAny(), It.IsInRange(TimeSpan.FromMinutes(4.9), TimeSpan.FromMinutes(5.1), Moq.Range.Inclusive)), Times.Once()); } [Test] public void Download_report_should_not_trigger_indexer_backoff_on_downloadclient_error() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Throws(new DownloadClientException("Some Error")); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock() .Verify(v => v.RecordFailure(It.IsAny(), It.IsAny()), Times.Never()); } [Test] public void Download_report_should_not_trigger_indexer_backoff_on_indexer_404_error() { var mock = WithUsenetClient(); mock.Setup(s => s.Download(It.IsAny(), It.IsAny())) .Callback((v, indexer) => { throw new ReleaseUnavailableException(v.Release, "Error", new WebException()); }); Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock() .Verify(v => v.RecordFailure(It.IsAny(), It.IsAny()), Times.Never()); } [Test] public void should_not_attempt_download_if_client_isnt_configured() { Assert.ThrowsAsync(async () => await Subject.DownloadReport(_parseResult)); Mocker.GetMock().Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Never()); VerifyEventNotPublished(); } [Test] public async Task should_attempt_download_even_if_client_is_disabled() { var mockUsenet = WithUsenetClient(); Mocker.GetMock() .Setup(v => v.GetBlockedProviders()) .Returns(new List { new DownloadClientStatus { ProviderId = _downloadClients.First().Definition.Id, DisabledTill = DateTime.UtcNow.AddHours(3) } }); await Subject.DownloadReport(_parseResult); Mocker.GetMock().Verify(c => c.GetBlockedProviders(), Times.Never()); mockUsenet.Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Once()); VerifyEventPublished(); } [Test] public async Task should_send_download_to_correct_usenet_client() { var mockTorrent = WithTorrentClient(); var mockUsenet = WithUsenetClient(); await Subject.DownloadReport(_parseResult); mockTorrent.Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Never()); mockUsenet.Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Once()); } [Test] public async Task should_send_download_to_correct_torrent_client() { var mockTorrent = WithTorrentClient(); var mockUsenet = WithUsenetClient(); _parseResult.Release.DownloadProtocol = nameof(TorrentDownloadProtocol); await Subject.DownloadReport(_parseResult); mockTorrent.Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Once()); mockUsenet.Verify(c => c.Download(It.IsAny(), It.IsAny()), Times.Never()); } } }