using System; using System.Collections.Generic; using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; using NzbDrone.Common.Extensions; using NzbDrone.Core.Exceptions; using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Music; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.MusicTests { [TestFixture] public class RefreshAlbumServiceFixture : CoreTest { private readonly List _fakeArtists = new List { new ArtistMetadata() }; private readonly string _fakeArtistForeignId = "xxx-xxx-xxx"; private Artist _artist; private List _albums; private List _releases; [SetUp] public void Setup() { var release = Builder .CreateNew() .With(s => s.Media = new List { new Medium { Number = 1 } }) .With(s => s.ForeignReleaseId = "xxx-xxx-xxx-xxx") .With(s => s.Monitored = true) .With(s => s.TrackCount = 10) .Build(); _releases = new List { release }; var album1 = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .With(s => s.Id = 1234) .With(s => s.ForeignAlbumId = "1") .With(s => s.AlbumReleases = _releases) .Build(); _albums = new List { album1 }; _artist = Builder.CreateNew() .With(s => s.Albums = _albums) .Build(); Mocker.GetMock() .Setup(s => s.GetArtist(_artist.Id)) .Returns(_artist); Mocker.GetMock() .Setup(s => s.GetReleasesForRefresh(album1.Id, It.IsAny>())) .Returns(new List { release }); Mocker.GetMock() .Setup(s => s.UpsertMany(It.IsAny>())) .Returns(true); Mocker.GetMock() .Setup(s => s.GetAlbumInfo(It.IsAny())) .Callback(() => { throw new AlbumNotFoundException(album1.ForeignAlbumId); }); Mocker.GetMock() .Setup(s => s.ShouldRefresh(It.IsAny())) .Returns(true); Mocker.GetMock() .Setup(x => x.GetFilesByAlbum(It.IsAny())) .Returns(new List()); Mocker.GetMock() .Setup(x => x.GetFilesByRelease(It.IsAny())) .Returns(new List()); Mocker.GetMock() .Setup(x => x.GetByAlbum(It.IsAny(), It.IsAny())) .Returns(new List()); } private void GivenNewAlbumInfo(Album album) { Mocker.GetMock() .Setup(s => s.GetAlbumInfo(_albums.First().ForeignAlbumId)) .Returns(new Tuple>(_fakeArtistForeignId, album, _fakeArtists)); } [Test] public void should_update_if_musicbrainz_id_changed_and_no_clash() { var newAlbumInfo = _albums.First().JsonClone(); newAlbumInfo.ArtistMetadata = _albums.First().ArtistMetadata.Value.JsonClone(); newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1; newAlbumInfo.AlbumReleases = _releases; GivenNewAlbumInfo(newAlbumInfo); Subject.RefreshAlbumInfo(_albums, null, false, false, null); Mocker.GetMock() .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId))); } [Test] public void should_merge_if_musicbrainz_id_changed_and_new_already_exists() { var existing = _albums.First(); var clash = existing.JsonClone(); clash.Id = 100; clash.ArtistMetadata = existing.ArtistMetadata.Value.JsonClone(); clash.ForeignAlbumId = clash.ForeignAlbumId + 1; clash.AlbumReleases = Builder.CreateListOfSize(10) .All().With(x => x.AlbumId = clash.Id) .BuildList(); Mocker.GetMock() .Setup(x => x.FindById(clash.ForeignAlbumId)) .Returns(clash); Mocker.GetMock() .Setup(x => x.GetReleasesByAlbum(_albums.First().Id)) .Returns(_releases); Mocker.GetMock() .Setup(x => x.GetReleasesByAlbum(clash.Id)) .Returns(new List()); Mocker.GetMock() .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(_releases); var newAlbumInfo = existing.JsonClone(); newAlbumInfo.ArtistMetadata = existing.ArtistMetadata.Value.JsonClone(); newAlbumInfo.ForeignAlbumId = _albums.First().ForeignAlbumId + 1; newAlbumInfo.AlbumReleases = _releases; GivenNewAlbumInfo(newAlbumInfo); Subject.RefreshAlbumInfo(_albums, null, false, false, null); // check releases moved to clashing album Mocker.GetMock() .Verify(v => v.UpdateMany(It.Is>(x => x.All(y => y.AlbumId == clash.Id) && x.Count == _releases.Count))); // check old album is deleted Mocker.GetMock() .Verify(v => v.DeleteMany(It.Is>(x => x.First().ForeignAlbumId == existing.ForeignAlbumId))); // check that clash gets updated Mocker.GetMock() .Verify(v => v.UpdateMany(It.Is>(s => s.First().ForeignAlbumId == newAlbumInfo.ForeignAlbumId))); ExceptionVerification.ExpectedWarns(1); } [Test] public void should_remove_album_with_no_valid_releases() { var album = _albums.First(); album.AlbumReleases = new List(); GivenNewAlbumInfo(album); Subject.RefreshAlbumInfo(album, null, false); Mocker.GetMock() .Verify(x => x.DeleteAlbum(album.Id, true, false), Times.Once()); ExceptionVerification.ExpectedWarns(1); } [Test] public void should_not_add_duplicate_releases() { var newAlbum = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // this is required because RefreshAlbumInfo will edit the album passed in var albumCopy = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); var releases = Builder.CreateListOfSize(10) .All() .With(x => x.AlbumId = newAlbum.Id) .With(x => x.Monitored = true) .TheFirst(4) .With(x => x.ForeignReleaseId = "DuplicateId1") .TheLast(1) .With(x => x.ForeignReleaseId = "DuplicateId2") .Build() as List; newAlbum.AlbumReleases = releases; albumCopy.AlbumReleases = releases; var existingReleases = Builder.CreateListOfSize(1) .TheFirst(1) .With(x => x.ForeignReleaseId = "DuplicateId2") .With(x => x.Monitored = true) .Build() as List; Mocker.GetMock() .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() .Setup(x => x.GetAlbumInfo(It.IsAny())) .Returns(Tuple.Create("dummy string", albumCopy, new List())); Subject.RefreshAlbumInfo(newAlbum, null, false); Mocker.GetMock() .Verify(x => x.RefreshEntityInfo(It.Is>(l => l.Count == 7 && l.Count(y => y.Monitored) == 1), It.IsAny>(), It.IsAny(), It.IsAny())); } [TestCase(true, true, 1)] [TestCase(true, false, 0)] [TestCase(false, true, 1)] [TestCase(false, false, 0)] public void should_only_leave_one_release_monitored(bool skyhookMonitored, bool existingMonitored, int expectedUpdates) { var newAlbum = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // this is required because RefreshAlbumInfo will edit the album passed in var albumCopy = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); var releases = Builder.CreateListOfSize(10) .All() .With(x => x.AlbumId = newAlbum.Id) .With(x => x.Monitored = skyhookMonitored) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .TheNext(1) .With(x => x.ForeignReleaseId = "ExistingId2") .Build() as List; newAlbum.AlbumReleases = releases; albumCopy.AlbumReleases = releases; var existingReleases = Builder.CreateListOfSize(2) .All() .With(x => x.Monitored = existingMonitored) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .TheNext(1) .With(x => x.ForeignReleaseId = "ExistingId2") .Build() as List; Mocker.GetMock() .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() .Setup(x => x.GetAlbumInfo(It.IsAny())) .Returns(Tuple.Create("dummy string", albumCopy, new List())); Subject.RefreshAlbumInfo(newAlbum, null, false); Mocker.GetMock() .Verify(x => x.RefreshEntityInfo(It.Is>(l => l.Count == 10 && l.Count(y => y.Monitored) == 1), It.IsAny>(), It.IsAny(), It.IsAny())); } [Test] public void refreshing_album_should_not_change_monitored_release_if_monitored_release_not_deleted() { var newAlbum = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // this is required because RefreshAlbumInfo will edit the album passed in var albumCopy = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // only ExistingId1 is monitored from dummy skyhook var releases = Builder.CreateListOfSize(10) .All() .With(x => x.AlbumId = newAlbum.Id) .With(x => x.Monitored = false) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .With(x => x.Monitored = true) .TheNext(1) .With(x => x.ForeignReleaseId = "ExistingId2") .Build() as List; newAlbum.AlbumReleases = releases; albumCopy.AlbumReleases = releases; // ExistingId2 is monitored in DB var existingReleases = Builder.CreateListOfSize(2) .All() .With(x => x.Monitored = false) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .TheNext(1) .With(x => x.ForeignReleaseId = "ExistingId2") .With(x => x.Monitored = true) .Build() as List; Mocker.GetMock() .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() .Setup(x => x.GetAlbumInfo(It.IsAny())) .Returns(Tuple.Create("dummy string", albumCopy, new List())); Subject.RefreshAlbumInfo(newAlbum, null, false); Mocker.GetMock() .Verify(x => x.RefreshEntityInfo(It.Is>( l => l.Count == 10 && l.Count(y => y.Monitored) == 1 && l.Single(y => y.Monitored).ForeignReleaseId == "ExistingId2"), It.IsAny>(), It.IsAny(), It.IsAny())); } [Test] public void refreshing_album_should_change_monitored_release_if_monitored_release_deleted() { var newAlbum = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // this is required because RefreshAlbumInfo will edit the album passed in var albumCopy = Builder.CreateNew() .With(x => x.ArtistMetadata = Builder.CreateNew().Build()) .Build(); // Only existingId1 monitored in skyhook. ExistingId2 is missing var releases = Builder.CreateListOfSize(10) .All() .With(x => x.AlbumId = newAlbum.Id) .With(x => x.Monitored = false) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .With(x => x.Monitored = true) .TheNext(1) .With(x => x.ForeignReleaseId = "NotExistingId2") .Build() as List; newAlbum.AlbumReleases = releases; albumCopy.AlbumReleases = releases; // ExistingId2 is monitored but will be deleted var existingReleases = Builder.CreateListOfSize(2) .All() .With(x => x.Monitored = false) .TheFirst(1) .With(x => x.ForeignReleaseId = "ExistingId1") .TheNext(1) .With(x => x.ForeignReleaseId = "ExistingId2") .With(x => x.Monitored = true) .Build() as List; Mocker.GetMock() .Setup(x => x.GetReleasesForRefresh(It.IsAny(), It.IsAny>())) .Returns(existingReleases); Mocker.GetMock() .Setup(x => x.GetAlbumInfo(It.IsAny())) .Returns(Tuple.Create("dummy string", albumCopy, new List())); Subject.RefreshAlbumInfo(newAlbum, null, false); Mocker.GetMock() .Verify(x => x.RefreshEntityInfo(It.Is>( l => l.Count == 11 && l.Count(y => y.Monitored) == 1 && l.Single(y => y.Monitored).ForeignReleaseId != "ExistingId2"), It.IsAny>(), It.IsAny(), It.IsAny())); } } }