using System.Collections.Generic; using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Tv; using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.Download { [TestFixture] public class CompletedDownloadServiceFixture : CoreTest { private TrackedDownload _trackedDownload; [SetUp] public void Setup() { var completed = Builder.CreateNew() .With(h => h.Status = DownloadItemStatus.Completed) .With(h => h.OutputPath = new OsPath(@"C:\DropFolder\MyDownload".AsOsAgnostic())) .With(h => h.Title = "Drone.S01E01.HDTV") .Build(); var remoteEpisode = BuildRemoteEpisode(); _trackedDownload = Builder.CreateNew() .With(c => c.State = TrackedDownloadStage.Downloading) .With(c => c.DownloadItem = completed) .With(c => c.RemoteEpisode = remoteEpisode) .Build(); Mocker.GetMock() .SetupGet(c => c.Definition) .Returns(new DownloadClientDefinition { Id = 1, Name = "testClient" }); Mocker.GetMock() .Setup(c => c.Get(It.IsAny())) .Returns(Mocker.GetMock().Object); Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) .Returns(new History.History()); Mocker.GetMock() .Setup(s => s.GetSeries("Drone.S01E01.HDTV")) .Returns(remoteEpisode.Series); } private RemoteEpisode BuildRemoteEpisode() { return new RemoteEpisode { Series = new Series(), Episodes = new List { new Episode { Id = 1 } } }; } private void GivenNoGrabbedHistory() { Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) .Returns((History.History)null); } private void GivenSuccessfulImport() { Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) }); } private void GivenABadlyNamedDownload() { _trackedDownload.DownloadItem.DownloadId = "1234"; _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))) .Returns(new History.History() { SourceTitle = "Droned S01E01" }); Mocker.GetMock() .Setup(s => s.GetSeries(It.IsAny())) .Returns((Series)null); Mocker.GetMock() .Setup(s => s.GetSeries("Droned S01E01")) .Returns(BuildRemoteEpisode().Series); } private void GivenSeriesMatch() { Mocker.GetMock() .Setup(s => s.GetSeries(It.IsAny())) .Returns(_trackedDownload.RemoteEpisode.Series); } [TestCase(DownloadItemStatus.Downloading)] [TestCase(DownloadItemStatus.Failed)] [TestCase(DownloadItemStatus.Queued)] [TestCase(DownloadItemStatus.Paused)] [TestCase(DownloadItemStatus.Warning)] public void should_not_process_if_download_status_isnt_completed(DownloadItemStatus status) { _trackedDownload.DownloadItem.Status = status; Subject.Process(_trackedDownload); AssertNoAttemptedImport(); } [Test] public void should_not_process_if_matching_history_is_not_found_and_no_category_specified() { _trackedDownload.DownloadItem.Category = null; GivenNoGrabbedHistory(); Subject.Process(_trackedDownload); AssertNoAttemptedImport(); } [Test] public void should_process_if_matching_history_is_not_found_but_category_specified() { _trackedDownload.DownloadItem.Category = "tv"; GivenNoGrabbedHistory(); GivenSeriesMatch(); GivenSuccessfulImport(); Subject.Process(_trackedDownload); AssertCompletedDownload(); } [Test] public void should_not_process_if_storage_directory_in_drone_factory() { Mocker.GetMock() .SetupGet(v => v.DownloadedEpisodesFolder) .Returns(@"C:\DropFolder".AsOsAgnostic()); _trackedDownload.DownloadItem.OutputPath = new OsPath(@"C:\DropFolder\SomeOtherFolder".AsOsAgnostic()); Subject.Process(_trackedDownload); AssertNoAttemptedImport(); } [Test] public void should_not_process_if_output_path_is_empty() { _trackedDownload.DownloadItem.OutputPath = new OsPath(); Subject.Process(_trackedDownload); AssertNoAttemptedImport(); } [Test] public void should_mark_as_imported_if_all_episodes_were_imported() { Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"})) }); Subject.Process(_trackedDownload); AssertCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_all_files_were_rejected() { Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") }); Subject.Process(_trackedDownload); Mocker.GetMock() .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); AssertNoCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_no_episodes_were_parsed() { Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") }); _trackedDownload.RemoteEpisode.Episodes.Clear(); Subject.Process(_trackedDownload); AssertNoCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_all_files_were_skipped() { Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") }); Subject.Process(_trackedDownload); AssertNoCompletedDownload(); } [Test] public void should_mark_as_imported_if_all_episodes_were_imported_but_extra_files_were_not() { GivenSeriesMatch(); _trackedDownload.RemoteEpisode.Episodes = new List { new Episode() }; Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") }); Subject.Process(_trackedDownload); AssertCompletedDownload(); } [Test] public void should_mark_as_failed_if_some_of_episodes_were_not_imported() { _trackedDownload.RemoteEpisode.Episodes = new List { new Episode(), new Episode(), new Episode() }; Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure") }); Subject.Process(_trackedDownload); AssertNoCompletedDownload(); } [Test] public void should_mark_as_imported_if_the_download_can_be_tracked_using_the_source_seriesid() { GivenABadlyNamedDownload(); Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) }); Mocker.GetMock() .Setup(v => v.GetSeries(It.IsAny())) .Returns(BuildRemoteEpisode().Series); Subject.Process(_trackedDownload); AssertCompletedDownload(); } [Test] public void should_not_mark_as_imported_if_the_download_cannot_be_tracked_using_the_source_title_as_it_was_initiated_externally() { GivenABadlyNamedDownload(); Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) }); Mocker.GetMock() .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))); Subject.Process(_trackedDownload); AssertNoCompletedDownload(); } [Test] public void should_not_import_when_there_is_a_title_mismatch() { Mocker.GetMock() .Setup(s => s.GetSeries("Drone.S01E01.HDTV")) .Returns((Series)null); Subject.Process(_trackedDownload); AssertNoCompletedDownload(); } [Test] public void should_mark_as_import_title_mismatch_if_ignore_warnings_is_true() { _trackedDownload.RemoteEpisode.Episodes = new List { new Episode() }; Mocker.GetMock() .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) }); Subject.Process(_trackedDownload, true); AssertCompletedDownload(); } private void AssertNoAttemptedImport() { Mocker.GetMock() .Verify(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); AssertNoCompletedDownload(); } private void AssertNoCompletedDownload() { Mocker.GetMock() .Verify(v => v.PublishEvent(It.IsAny()), Times.Never()); _trackedDownload.State.Should().NotBe(TrackedDownloadStage.Imported); } private void AssertCompletedDownload() { Mocker.GetMock() .Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once()); Mocker.GetMock() .Verify(v => v.PublishEvent(It.IsAny()), Times.Once()); _trackedDownload.State.Should().Be(TrackedDownloadStage.Imported); } } }