diff --git a/.gitattributes b/.gitattributes index 33c6e6c2d..9580abc23 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,11 @@ # Auto detect text files and perform LF normalization -*text eol=lf +* text=auto + +# Explicitly set bash scripts to have unix endings +# when checked out on windows +*.sh text eol=lf +distribution/debian/* text eol=lf +macOS/Sonarr text eol=lf # Custom for Visual Studio *.cs diff=csharp diff --git a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs index 4fb68adc4..381e074ed 100644 --- a/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/RefreshEpisodeServiceFixture.cs @@ -1,398 +1,398 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.MetadataSource.SkyHook; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.TvTests -{ - [TestFixture] - public class RefreshEpisodeServiceFixture : CoreTest - { - private List _insertedEpisodes; - private List _updatedEpisodes; - private List _deletedEpisodes; - private Tuple> _gameOfThrones; - - [OneTimeSetUp] - public void TestFixture() - { - UseRealHttp(); - - _gameOfThrones = Mocker.Resolve().GetSeriesInfo(121361);//Game of thrones - - // Remove specials. - _gameOfThrones.Item2.RemoveAll(v => v.SeasonNumber == 0); - } - - private List GetEpisodes() - { - return _gameOfThrones.Item2.JsonClone(); - } - - private Series GetSeries() - { - var series = _gameOfThrones.Item1.JsonClone(); - series.Seasons = new List(); - - return series; - } - - private Series GetAnimeSeries() - { - var series = Builder.CreateNew().Build(); - series.SeriesType = SeriesTypes.Anime; - series.Seasons = new List(); - - return series; - } - - [SetUp] - public void Setup() - { - _insertedEpisodes = new List(); - _updatedEpisodes = new List(); - _deletedEpisodes = new List(); - - Mocker.GetMock().Setup(c => c.InsertMany(It.IsAny>())) - .Callback>(e => _insertedEpisodes = e); - - - Mocker.GetMock().Setup(c => c.UpdateMany(It.IsAny>())) - .Callback>(e => _updatedEpisodes = e); - - - Mocker.GetMock().Setup(c => c.DeleteMany(It.IsAny>())) - .Callback>(e => _deletedEpisodes = e); - } - - [Test] - public void should_create_all_when_no_existing_episodes() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); - - _insertedEpisodes.Should().HaveSameCount(GetEpisodes()); - _updatedEpisodes.Should().BeEmpty(); - _deletedEpisodes.Should().BeEmpty(); - } - - [Test] - public void should_update_all_when_all_existing_episodes() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(GetEpisodes()); - - Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); - - _insertedEpisodes.Should().BeEmpty(); - _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); - _deletedEpisodes.Should().BeEmpty(); - } - - [Test] - public void should_delete_all_when_all_existing_episodes_are_gone_from_datasource() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(GetEpisodes()); - - Subject.RefreshEpisodeInfo(GetSeries(), new List()); - - _insertedEpisodes.Should().BeEmpty(); - _updatedEpisodes.Should().BeEmpty(); - _deletedEpisodes.Should().HaveSameCount(GetEpisodes()); - } - - [Test] - public void should_delete_duplicated_episodes_based_on_season_episode_number() - { - var duplicateEpisodes = GetEpisodes().Skip(5).Take(2).ToList(); - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(GetEpisodes().Union(duplicateEpisodes).ToList()); - - Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); - - _insertedEpisodes.Should().BeEmpty(); - _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); - _deletedEpisodes.Should().HaveSameCount(duplicateEpisodes); - } - - [Test] - public void should_not_change_monitored_status_for_existing_episodes() - { - var series = GetSeries(); - series.Seasons = new List(); - series.Seasons.Add(new Season { SeasonNumber = 1, Monitored = false }); - - var episodes = GetEpisodes(); - - episodes.ForEach(e => e.Monitored = true); - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(episodes); - - Subject.RefreshEpisodeInfo(series, GetEpisodes()); - - _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); - _updatedEpisodes.Should().OnlyContain(e => e.Monitored == true); - } - - [Test] - public void should_remove_duplicate_remote_episodes_before_processing() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - var episodes = Builder.CreateListOfSize(5) - .TheFirst(2) - .With(e => e.SeasonNumber = 1) - .With(e => e.EpisodeNumber = 1) - .Build() - .ToList(); - - Subject.RefreshEpisodeInfo(GetSeries(), episodes); - - _insertedEpisodes.Should().HaveCount(episodes.Count - 1); - _updatedEpisodes.Should().BeEmpty(); - _deletedEpisodes.Should().BeEmpty(); - } - - [Test] - public void should_set_absolute_episode_number_for_anime() - { - var episodes = Builder.CreateListOfSize(3).Build().ToList(); - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - - _insertedEpisodes.All(e => e.AbsoluteEpisodeNumber.HasValue).Should().BeTrue(); - _updatedEpisodes.Should().BeEmpty(); - _deletedEpisodes.Should().BeEmpty(); - } - - [Test] - public void should_set_absolute_episode_number_even_if_not_previously_set_for_anime() - { - var episodes = Builder.CreateListOfSize(3).Build().ToList(); - - var existingEpisodes = episodes.JsonClone(); - existingEpisodes.ForEach(e => e.AbsoluteEpisodeNumber = null); - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(existingEpisodes); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - - _insertedEpisodes.Should().BeEmpty(); - _updatedEpisodes.All(e => e.AbsoluteEpisodeNumber.HasValue).Should().BeTrue(); - _deletedEpisodes.Should().BeEmpty(); - } - - [Test] - public void should_get_new_season_and_episode_numbers_when_absolute_episode_number_match_found() - { - const int expectedSeasonNumber = 10; - const int expectedEpisodeNumber = 5; - const int expectedAbsoluteNumber = 3; - - var episode = Builder.CreateNew() - .With(e => e.SeasonNumber = expectedSeasonNumber) - .With(e => e.EpisodeNumber = expectedEpisodeNumber) - .With(e => e.AbsoluteEpisodeNumber = expectedAbsoluteNumber) - .Build(); - - var existingEpisode = episode.JsonClone(); - existingEpisode.SeasonNumber = 1; - existingEpisode.EpisodeNumber = 1; - existingEpisode.AbsoluteEpisodeNumber = expectedAbsoluteNumber; - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List{ existingEpisode }); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), new List { episode }); - - _insertedEpisodes.Should().BeEmpty(); - _deletedEpisodes.Should().BeEmpty(); - - _updatedEpisodes.First().SeasonNumber.Should().Be(expectedSeasonNumber); - _updatedEpisodes.First().EpisodeNumber.Should().Be(expectedEpisodeNumber); - _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(expectedAbsoluteNumber); - } - - [Test] - public void should_prefer_absolute_match_over_season_and_epsiode_match() - { - var episodes = Builder.CreateListOfSize(2) - .Build() - .ToList(); - - episodes[0].AbsoluteEpisodeNumber = null; - episodes[0].SeasonNumber.Should().NotBe(episodes[1].SeasonNumber); - episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber); - - var existingEpisode = new Episode - { - SeasonNumber = episodes[0].SeasonNumber, - EpisodeNumber = episodes[0].EpisodeNumber, - AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber - }; - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List { existingEpisode }); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - - _updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber); - _updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber); - _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber); - } - - [Test] - public void should_ignore_episodes_with_no_absolute_episode_in_distinct_by_absolute() - { - var episodes = Builder.CreateListOfSize(10) - .Build() - .ToList(); - - episodes[0].AbsoluteEpisodeNumber = null; - episodes[1].AbsoluteEpisodeNumber = null; - episodes[2].AbsoluteEpisodeNumber = null; - episodes[3].AbsoluteEpisodeNumber = null; - episodes[4].AbsoluteEpisodeNumber = null; - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - - _insertedEpisodes.Should().HaveCount(episodes.Count); - - } - - [Test] - public void should_override_empty_airdate_for_direct_to_dvd() - { - var series = GetSeries(); - series.Status = SeriesStatusType.Ended; - - var episodes = Builder.CreateListOfSize(10) - .All() - .With(v => v.AirDateUtc = null) - .BuildListOfNew(); - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - List updateEpisodes = null; - Mocker.GetMock().Setup(c => c.InsertMany(It.IsAny>())) - .Callback>(c => updateEpisodes = c); - - Subject.RefreshEpisodeInfo(series, episodes); - - updateEpisodes.Should().NotBeNull(); - updateEpisodes.Should().NotBeEmpty(); - updateEpisodes.All(v => v.AirDateUtc.HasValue).Should().BeTrue(); - } - - [Test] - public void should_use_tba_for_episode_title_when_null() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - var episodes = Builder.CreateListOfSize(1) - .All() - .With(e => e.Title = null) - .Build() - .ToList(); - - Subject.RefreshEpisodeInfo(GetSeries(), episodes); - - _insertedEpisodes.First().Title.Should().Be("TBA"); - } - - [Test] - public void should_update_air_date_when_multiple_episodes_air_on_the_same_day() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - var now = DateTime.UtcNow; - var series = GetSeries(); - - var episodes = Builder.CreateListOfSize(2) - .All() - .With(e => e.SeasonNumber = 1) - .With(e => e.AirDate = now.ToShortDateString()) - .With(e => e.AirDateUtc = now) - .Build() - .ToList(); - - Subject.RefreshEpisodeInfo(series, episodes); - - _insertedEpisodes.First().AirDateUtc.Value.ToString("s").Should().Be(episodes.First().AirDateUtc.Value.ToString("s")); - _insertedEpisodes.Last().AirDateUtc.Value.ToString("s").Should().Be(episodes.First().AirDateUtc.Value.AddMinutes(series.Runtime).ToString("s")); - } - - [Test] - public void should_not_update_air_date_when_more_than_three_episodes_air_on_the_same_day() - { - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List()); - - var now = DateTime.UtcNow; - var series = GetSeries(); - - var episodes = Builder.CreateListOfSize(4) - .All() - .With(e => e.SeasonNumber = 1) - .With(e => e.AirDate = now.ToShortDateString()) - .With(e => e.AirDateUtc = now) - .Build() - .ToList(); - - Subject.RefreshEpisodeInfo(series, episodes); - - _insertedEpisodes.Should().OnlyContain(e => e.AirDateUtc.Value.ToString("s") == episodes.First().AirDateUtc.Value.ToString("s")); - } - - [Test] - public void should_prefer_regular_season_when_absolute_numbers_conflict() - { - var episodes = Builder.CreateListOfSize(2) - .Build() - .ToList(); - - episodes[0].AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber; - episodes[0].SeasonNumber = 0; - episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber); - - var existingEpisode = new Episode - { - SeasonNumber = episodes[0].SeasonNumber, - EpisodeNumber = episodes[0].EpisodeNumber, - AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber - }; - - Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) - .Returns(new List { existingEpisode }); - - Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); - - _updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber); - _updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber); - _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber); - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MetadataSource.SkyHook; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.TvTests +{ + [TestFixture] + public class RefreshEpisodeServiceFixture : CoreTest + { + private List _insertedEpisodes; + private List _updatedEpisodes; + private List _deletedEpisodes; + private Tuple> _gameOfThrones; + + [OneTimeSetUp] + public void TestFixture() + { + UseRealHttp(); + + _gameOfThrones = Mocker.Resolve().GetSeriesInfo(121361);//Game of thrones + + // Remove specials. + _gameOfThrones.Item2.RemoveAll(v => v.SeasonNumber == 0); + } + + private List GetEpisodes() + { + return _gameOfThrones.Item2.JsonClone(); + } + + private Series GetSeries() + { + var series = _gameOfThrones.Item1.JsonClone(); + series.Seasons = new List(); + + return series; + } + + private Series GetAnimeSeries() + { + var series = Builder.CreateNew().Build(); + series.SeriesType = SeriesTypes.Anime; + series.Seasons = new List(); + + return series; + } + + [SetUp] + public void Setup() + { + _insertedEpisodes = new List(); + _updatedEpisodes = new List(); + _deletedEpisodes = new List(); + + Mocker.GetMock().Setup(c => c.InsertMany(It.IsAny>())) + .Callback>(e => _insertedEpisodes = e); + + + Mocker.GetMock().Setup(c => c.UpdateMany(It.IsAny>())) + .Callback>(e => _updatedEpisodes = e); + + + Mocker.GetMock().Setup(c => c.DeleteMany(It.IsAny>())) + .Callback>(e => _deletedEpisodes = e); + } + + [Test] + public void should_create_all_when_no_existing_episodes() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); + + _insertedEpisodes.Should().HaveSameCount(GetEpisodes()); + _updatedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_update_all_when_all_existing_episodes() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(GetEpisodes()); + + Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); + + _insertedEpisodes.Should().BeEmpty(); + _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_delete_all_when_all_existing_episodes_are_gone_from_datasource() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(GetEpisodes()); + + Subject.RefreshEpisodeInfo(GetSeries(), new List()); + + _insertedEpisodes.Should().BeEmpty(); + _updatedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().HaveSameCount(GetEpisodes()); + } + + [Test] + public void should_delete_duplicated_episodes_based_on_season_episode_number() + { + var duplicateEpisodes = GetEpisodes().Skip(5).Take(2).ToList(); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(GetEpisodes().Union(duplicateEpisodes).ToList()); + + Subject.RefreshEpisodeInfo(GetSeries(), GetEpisodes()); + + _insertedEpisodes.Should().BeEmpty(); + _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); + _deletedEpisodes.Should().HaveSameCount(duplicateEpisodes); + } + + [Test] + public void should_not_change_monitored_status_for_existing_episodes() + { + var series = GetSeries(); + series.Seasons = new List(); + series.Seasons.Add(new Season { SeasonNumber = 1, Monitored = false }); + + var episodes = GetEpisodes(); + + episodes.ForEach(e => e.Monitored = true); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(episodes); + + Subject.RefreshEpisodeInfo(series, GetEpisodes()); + + _updatedEpisodes.Should().HaveSameCount(GetEpisodes()); + _updatedEpisodes.Should().OnlyContain(e => e.Monitored == true); + } + + [Test] + public void should_remove_duplicate_remote_episodes_before_processing() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + var episodes = Builder.CreateListOfSize(5) + .TheFirst(2) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 1) + .Build() + .ToList(); + + Subject.RefreshEpisodeInfo(GetSeries(), episodes); + + _insertedEpisodes.Should().HaveCount(episodes.Count - 1); + _updatedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_set_absolute_episode_number_for_anime() + { + var episodes = Builder.CreateListOfSize(3).Build().ToList(); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _insertedEpisodes.All(e => e.AbsoluteEpisodeNumber.HasValue).Should().BeTrue(); + _updatedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_set_absolute_episode_number_even_if_not_previously_set_for_anime() + { + var episodes = Builder.CreateListOfSize(3).Build().ToList(); + + var existingEpisodes = episodes.JsonClone(); + existingEpisodes.ForEach(e => e.AbsoluteEpisodeNumber = null); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(existingEpisodes); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _insertedEpisodes.Should().BeEmpty(); + _updatedEpisodes.All(e => e.AbsoluteEpisodeNumber.HasValue).Should().BeTrue(); + _deletedEpisodes.Should().BeEmpty(); + } + + [Test] + public void should_get_new_season_and_episode_numbers_when_absolute_episode_number_match_found() + { + const int expectedSeasonNumber = 10; + const int expectedEpisodeNumber = 5; + const int expectedAbsoluteNumber = 3; + + var episode = Builder.CreateNew() + .With(e => e.SeasonNumber = expectedSeasonNumber) + .With(e => e.EpisodeNumber = expectedEpisodeNumber) + .With(e => e.AbsoluteEpisodeNumber = expectedAbsoluteNumber) + .Build(); + + var existingEpisode = episode.JsonClone(); + existingEpisode.SeasonNumber = 1; + existingEpisode.EpisodeNumber = 1; + existingEpisode.AbsoluteEpisodeNumber = expectedAbsoluteNumber; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List{ existingEpisode }); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), new List { episode }); + + _insertedEpisodes.Should().BeEmpty(); + _deletedEpisodes.Should().BeEmpty(); + + _updatedEpisodes.First().SeasonNumber.Should().Be(expectedSeasonNumber); + _updatedEpisodes.First().EpisodeNumber.Should().Be(expectedEpisodeNumber); + _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(expectedAbsoluteNumber); + } + + [Test] + public void should_prefer_absolute_match_over_season_and_epsiode_match() + { + var episodes = Builder.CreateListOfSize(2) + .Build() + .ToList(); + + episodes[0].AbsoluteEpisodeNumber = null; + episodes[0].SeasonNumber.Should().NotBe(episodes[1].SeasonNumber); + episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber); + + var existingEpisode = new Episode + { + SeasonNumber = episodes[0].SeasonNumber, + EpisodeNumber = episodes[0].EpisodeNumber, + AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber + }; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List { existingEpisode }); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber); + _updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber); + _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber); + } + + [Test] + public void should_ignore_episodes_with_no_absolute_episode_in_distinct_by_absolute() + { + var episodes = Builder.CreateListOfSize(10) + .Build() + .ToList(); + + episodes[0].AbsoluteEpisodeNumber = null; + episodes[1].AbsoluteEpisodeNumber = null; + episodes[2].AbsoluteEpisodeNumber = null; + episodes[3].AbsoluteEpisodeNumber = null; + episodes[4].AbsoluteEpisodeNumber = null; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _insertedEpisodes.Should().HaveCount(episodes.Count); + + } + + [Test] + public void should_override_empty_airdate_for_direct_to_dvd() + { + var series = GetSeries(); + series.Status = SeriesStatusType.Ended; + + var episodes = Builder.CreateListOfSize(10) + .All() + .With(v => v.AirDateUtc = null) + .BuildListOfNew(); + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + List updateEpisodes = null; + Mocker.GetMock().Setup(c => c.InsertMany(It.IsAny>())) + .Callback>(c => updateEpisodes = c); + + Subject.RefreshEpisodeInfo(series, episodes); + + updateEpisodes.Should().NotBeNull(); + updateEpisodes.Should().NotBeEmpty(); + updateEpisodes.All(v => v.AirDateUtc.HasValue).Should().BeTrue(); + } + + [Test] + public void should_use_tba_for_episode_title_when_null() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + var episodes = Builder.CreateListOfSize(1) + .All() + .With(e => e.Title = null) + .Build() + .ToList(); + + Subject.RefreshEpisodeInfo(GetSeries(), episodes); + + _insertedEpisodes.First().Title.Should().Be("TBA"); + } + + [Test] + public void should_update_air_date_when_multiple_episodes_air_on_the_same_day() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + var now = DateTime.UtcNow; + var series = GetSeries(); + + var episodes = Builder.CreateListOfSize(2) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.AirDate = now.ToShortDateString()) + .With(e => e.AirDateUtc = now) + .Build() + .ToList(); + + Subject.RefreshEpisodeInfo(series, episodes); + + _insertedEpisodes.First().AirDateUtc.Value.ToString("s").Should().Be(episodes.First().AirDateUtc.Value.ToString("s")); + _insertedEpisodes.Last().AirDateUtc.Value.ToString("s").Should().Be(episodes.First().AirDateUtc.Value.AddMinutes(series.Runtime).ToString("s")); + } + + [Test] + public void should_not_update_air_date_when_more_than_three_episodes_air_on_the_same_day() + { + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List()); + + var now = DateTime.UtcNow; + var series = GetSeries(); + + var episodes = Builder.CreateListOfSize(4) + .All() + .With(e => e.SeasonNumber = 1) + .With(e => e.AirDate = now.ToShortDateString()) + .With(e => e.AirDateUtc = now) + .Build() + .ToList(); + + Subject.RefreshEpisodeInfo(series, episodes); + + _insertedEpisodes.Should().OnlyContain(e => e.AirDateUtc.Value.ToString("s") == episodes.First().AirDateUtc.Value.ToString("s")); + } + + [Test] + public void should_prefer_regular_season_when_absolute_numbers_conflict() + { + var episodes = Builder.CreateListOfSize(2) + .Build() + .ToList(); + + episodes[0].AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber; + episodes[0].SeasonNumber = 0; + episodes[0].EpisodeNumber.Should().NotBe(episodes[1].EpisodeNumber); + + var existingEpisode = new Episode + { + SeasonNumber = episodes[0].SeasonNumber, + EpisodeNumber = episodes[0].EpisodeNumber, + AbsoluteEpisodeNumber = episodes[1].AbsoluteEpisodeNumber + }; + + Mocker.GetMock().Setup(c => c.GetEpisodeBySeries(It.IsAny())) + .Returns(new List { existingEpisode }); + + Subject.RefreshEpisodeInfo(GetAnimeSeries(), episodes); + + _updatedEpisodes.First().SeasonNumber.Should().Be(episodes[1].SeasonNumber); + _updatedEpisodes.First().EpisodeNumber.Should().Be(episodes[1].EpisodeNumber); + _updatedEpisodes.First().AbsoluteEpisodeNumber.Should().Be(episodes[1].AbsoluteEpisodeNumber); + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs index a5f10f68f..81dbdce75 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateMultipleSeriesFixture.cs @@ -1,84 +1,84 @@ -using System.Collections.Generic; -using System.IO; -using System.Linq; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.RootFolders; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Test.Common; - -namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests -{ - [TestFixture] - public class UpdateMultipleSeriesFixture : CoreTest - { - private List _series; - - [SetUp] - public void Setup() - { - _series = Builder.CreateListOfSize(5) - .All() - .With(s => s.QualityProfileId = 1) - .With(s => s.Monitored) - .With(s => s.SeasonFolder) - .With(s => s.Path = @"C:\Test\name".AsOsAgnostic()) - .With(s => s.RootFolderPath = "") - .Build().ToList(); - } - - [Test] - public void should_call_repo_updateMany() - { - Subject.UpdateSeries(_series, false); - - Mocker.GetMock().Verify(v => v.UpdateMany(_series), Times.Once()); - } - - [Test] - public void should_update_path_when_rootFolderPath_is_supplied() - { - var newRoot = @"C:\Test\TV2".AsOsAgnostic(); - _series.ForEach(s => s.RootFolderPath = newRoot); - - Mocker.GetMock() - .Setup(s => s.BuildPath(It.IsAny(), false)) - .Returns((s, u) => Path.Combine(s.RootFolderPath, s.Title)); - - Subject.UpdateSeries(_series, false).ForEach(s => s.Path.Should().StartWith(newRoot)); - } - - [Test] - public void should_not_update_path_when_rootFolderPath_is_empty() - { - Subject.UpdateSeries(_series, false).ForEach(s => - { - var expectedPath = _series.Single(ser => ser.Id == s.Id).Path; - s.Path.Should().Be(expectedPath); - }); - } - - [Test] - public void should_be_able_to_update_many_series() - { - var series = Builder.CreateListOfSize(50) - .All() - .With(s => s.Path = (@"C:\Test\TV\" + s.Path).AsOsAgnostic()) - .Build() - .ToList(); - - var newRoot = @"C:\Test\TV2".AsOsAgnostic(); - series.ForEach(s => s.RootFolderPath = newRoot); - - Mocker.GetMock() - .Setup(s => s.GetSeriesFolder(It.IsAny(), (NamingConfig)null)) - .Returns((s, n) => s.Title); - - Subject.UpdateSeries(series, false); - } - } -} +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.RootFolders; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests +{ + [TestFixture] + public class UpdateMultipleSeriesFixture : CoreTest + { + private List _series; + + [SetUp] + public void Setup() + { + _series = Builder.CreateListOfSize(5) + .All() + .With(s => s.QualityProfileId = 1) + .With(s => s.Monitored) + .With(s => s.SeasonFolder) + .With(s => s.Path = @"C:\Test\name".AsOsAgnostic()) + .With(s => s.RootFolderPath = "") + .Build().ToList(); + } + + [Test] + public void should_call_repo_updateMany() + { + Subject.UpdateSeries(_series, false); + + Mocker.GetMock().Verify(v => v.UpdateMany(_series), Times.Once()); + } + + [Test] + public void should_update_path_when_rootFolderPath_is_supplied() + { + var newRoot = @"C:\Test\TV2".AsOsAgnostic(); + _series.ForEach(s => s.RootFolderPath = newRoot); + + Mocker.GetMock() + .Setup(s => s.BuildPath(It.IsAny(), false)) + .Returns((s, u) => Path.Combine(s.RootFolderPath, s.Title)); + + Subject.UpdateSeries(_series, false).ForEach(s => s.Path.Should().StartWith(newRoot)); + } + + [Test] + public void should_not_update_path_when_rootFolderPath_is_empty() + { + Subject.UpdateSeries(_series, false).ForEach(s => + { + var expectedPath = _series.Single(ser => ser.Id == s.Id).Path; + s.Path.Should().Be(expectedPath); + }); + } + + [Test] + public void should_be_able_to_update_many_series() + { + var series = Builder.CreateListOfSize(50) + .All() + .With(s => s.Path = (@"C:\Test\TV\" + s.Path).AsOsAgnostic()) + .Build() + .ToList(); + + var newRoot = @"C:\Test\TV2".AsOsAgnostic(); + series.ForEach(s => s.RootFolderPath = newRoot); + + Mocker.GetMock() + .Setup(s => s.GetSeriesFolder(It.IsAny(), (NamingConfig)null)) + .Returns((s, n) => s.Title); + + Subject.UpdateSeries(series, false); + } + } +} diff --git a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs index 23f77223c..cfe23d4ca 100644 --- a/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs +++ b/src/NzbDrone.Core.Test/TvTests/SeriesServiceTests/UpdateSeriesFixture.cs @@ -1,72 +1,72 @@ -using System.Collections.Generic; -using System.Linq; -using FizzWare.NBuilder; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests -{ - [TestFixture] - public class UpdateSeriesFixture : CoreTest - { - private Series _fakeSeries; - private Series _existingSeries; - - [SetUp] - public void Setup() - { - _fakeSeries = Builder.CreateNew().Build(); - _existingSeries = Builder.CreateNew().Build(); - - _fakeSeries.Seasons = new List - { - new Season{ SeasonNumber = 1, Monitored = true }, - new Season{ SeasonNumber = 2, Monitored = true } - }; - - _existingSeries.Seasons = new List - { - new Season{ SeasonNumber = 1, Monitored = true }, - new Season{ SeasonNumber = 2, Monitored = true } - }; - } - - private void GivenExistingSeries() - { - Mocker.GetMock() - .Setup(s => s.Get(It.IsAny())) - .Returns(_existingSeries); - } - - [Test] - public void should_not_update_episodes_if_season_hasnt_changed() - { - GivenExistingSeries(); - - Subject.UpdateSeries(_fakeSeries); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, It.IsAny(), It.IsAny()), Times.Never()); - } - - [Test] - public void should_update_series_when_it_changes() - { - GivenExistingSeries(); - var seasonNumber = 1; - var monitored = false; - - _fakeSeries.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored = monitored; - - Subject.UpdateSeries(_fakeSeries); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, seasonNumber, monitored), Times.Once()); - - Mocker.GetMock() - .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, It.IsAny(), It.IsAny()), Times.Once()); - } - } +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.TvTests.SeriesServiceTests +{ + [TestFixture] + public class UpdateSeriesFixture : CoreTest + { + private Series _fakeSeries; + private Series _existingSeries; + + [SetUp] + public void Setup() + { + _fakeSeries = Builder.CreateNew().Build(); + _existingSeries = Builder.CreateNew().Build(); + + _fakeSeries.Seasons = new List + { + new Season{ SeasonNumber = 1, Monitored = true }, + new Season{ SeasonNumber = 2, Monitored = true } + }; + + _existingSeries.Seasons = new List + { + new Season{ SeasonNumber = 1, Monitored = true }, + new Season{ SeasonNumber = 2, Monitored = true } + }; + } + + private void GivenExistingSeries() + { + Mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(_existingSeries); + } + + [Test] + public void should_not_update_episodes_if_season_hasnt_changed() + { + GivenExistingSeries(); + + Subject.UpdateSeries(_fakeSeries); + + Mocker.GetMock() + .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, It.IsAny(), It.IsAny()), Times.Never()); + } + + [Test] + public void should_update_series_when_it_changes() + { + GivenExistingSeries(); + var seasonNumber = 1; + var monitored = false; + + _fakeSeries.Seasons.Single(s => s.SeasonNumber == seasonNumber).Monitored = monitored; + + Subject.UpdateSeries(_fakeSeries); + + Mocker.GetMock() + .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, seasonNumber, monitored), Times.Once()); + + Mocker.GetMock() + .Verify(v => v.SetEpisodeMonitoredBySeason(_fakeSeries.Id, It.IsAny(), It.IsAny()), Times.Once()); + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs index 48c9710c5..4ba37f66d 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs @@ -1,7 +1,7 @@ -namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses -{ - public class SabnzbdFullStatusResponse - { - public SabnzbdFullStatus Status { get; set; } - } -} +namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses +{ + public class SabnzbdFullStatusResponse + { + public SabnzbdFullStatus Status { get; set; } + } +} diff --git a/src/Sonarr.Http/Extensions/Pipelines/RequestLoggingPipeline.cs b/src/Sonarr.Http/Extensions/Pipelines/RequestLoggingPipeline.cs index 200c4e299..067bc45e6 100644 --- a/src/Sonarr.Http/Extensions/Pipelines/RequestLoggingPipeline.cs +++ b/src/Sonarr.Http/Extensions/Pipelines/RequestLoggingPipeline.cs @@ -1,93 +1,93 @@ -using System; -using System.Threading; -using Nancy; -using Nancy.Bootstrapper; -using NLog; -using NzbDrone.Common.Extensions; -using Sonarr.Http.ErrorManagement; -using Sonarr.Http.Extensions; -using Sonarr.Http.Extensions.Pipelines; - -namespace NzbDrone.Api.Extensions.Pipelines -{ - public class RequestLoggingPipeline : IRegisterNancyPipeline - { - private static readonly Logger _loggerHttp = LogManager.GetLogger("Http"); - private static readonly Logger _loggerApi = LogManager.GetLogger("Api"); - - private static int _requestSequenceID; - - private readonly SonarrErrorPipeline _errorPipeline; - - public RequestLoggingPipeline(SonarrErrorPipeline errorPipeline) - { - _errorPipeline = errorPipeline; - } - - public int Order => 100; - - public void Register(IPipelines pipelines) - { - pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart); - pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd); - pipelines.OnError.AddItemToEndOfPipeline(LogError); - } - - private Response LogStart(NancyContext context) - { - var id = Interlocked.Increment(ref _requestSequenceID); - - context.Items["ApiRequestSequenceID"] = id; - context.Items["ApiRequestStartTime"] = DateTime.UtcNow; - - var reqPath = GetRequestPathAndQuery(context.Request); - - _loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath); - - return null; - } - - private void LogEnd(NancyContext context) - { - var id = (int)context.Items["ApiRequestSequenceID"]; - var startTime = (DateTime)context.Items["ApiRequestStartTime"]; - - var endTime = DateTime.UtcNow; - var duration = endTime - startTime; - - var reqPath = GetRequestPathAndQuery(context.Request); - - _loggerHttp.Trace("Res: {0} [{1}] {2}: {3}.{4} ({5} ms)", id, context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); - - if (context.Request.IsApiRequest()) - { - _loggerApi.Debug("[{0}] {1}: {2}.{3} ({4} ms)", context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); - } - } - - private Response LogError(NancyContext context, Exception exception) - { - var response = _errorPipeline.HandleException(context, exception); - - context.Response = response; - - LogEnd(context); - - context.Response = null; - - return response; - } - - private static string GetRequestPathAndQuery(Request request) - { - if (request.Url.Query.IsNotNullOrWhiteSpace()) - { - return string.Concat(request.Url.Path, request.Url.Query); - } - else - { - return request.Url.Path; - } - } - } -} +using System; +using System.Threading; +using Nancy; +using Nancy.Bootstrapper; +using NLog; +using NzbDrone.Common.Extensions; +using Sonarr.Http.ErrorManagement; +using Sonarr.Http.Extensions; +using Sonarr.Http.Extensions.Pipelines; + +namespace NzbDrone.Api.Extensions.Pipelines +{ + public class RequestLoggingPipeline : IRegisterNancyPipeline + { + private static readonly Logger _loggerHttp = LogManager.GetLogger("Http"); + private static readonly Logger _loggerApi = LogManager.GetLogger("Api"); + + private static int _requestSequenceID; + + private readonly SonarrErrorPipeline _errorPipeline; + + public RequestLoggingPipeline(SonarrErrorPipeline errorPipeline) + { + _errorPipeline = errorPipeline; + } + + public int Order => 100; + + public void Register(IPipelines pipelines) + { + pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart); + pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd); + pipelines.OnError.AddItemToEndOfPipeline(LogError); + } + + private Response LogStart(NancyContext context) + { + var id = Interlocked.Increment(ref _requestSequenceID); + + context.Items["ApiRequestSequenceID"] = id; + context.Items["ApiRequestStartTime"] = DateTime.UtcNow; + + var reqPath = GetRequestPathAndQuery(context.Request); + + _loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath); + + return null; + } + + private void LogEnd(NancyContext context) + { + var id = (int)context.Items["ApiRequestSequenceID"]; + var startTime = (DateTime)context.Items["ApiRequestStartTime"]; + + var endTime = DateTime.UtcNow; + var duration = endTime - startTime; + + var reqPath = GetRequestPathAndQuery(context.Request); + + _loggerHttp.Trace("Res: {0} [{1}] {2}: {3}.{4} ({5} ms)", id, context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); + + if (context.Request.IsApiRequest()) + { + _loggerApi.Debug("[{0}] {1}: {2}.{3} ({4} ms)", context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); + } + } + + private Response LogError(NancyContext context, Exception exception) + { + var response = _errorPipeline.HandleException(context, exception); + + context.Response = response; + + LogEnd(context); + + context.Response = null; + + return response; + } + + private static string GetRequestPathAndQuery(Request request) + { + if (request.Url.Query.IsNotNullOrWhiteSpace()) + { + return string.Concat(request.Url.Path, request.Url.Query); + } + else + { + return request.Url.Path; + } + } + } +}