using System.Collections.Generic; using FizzWare.NBuilder; using Moq; using NUnit.Framework; using NzbDrone.Common.Extensions; using NzbDrone.Core.Books; using NzbDrone.Core.Books.Commands; using NzbDrone.Core.Books.Events; using NzbDrone.Core.Exceptions; using NzbDrone.Core.History; using NzbDrone.Core.ImportLists.Exclusions; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MetadataSource; using NzbDrone.Core.Profiles.Metadata; using NzbDrone.Core.RootFolders; using NzbDrone.Core.Test.Framework; using NzbDrone.Test.Common; namespace NzbDrone.Core.Test.MusicTests { [TestFixture] public class RefreshAuthorServiceFixture : CoreTest { private Author _author; private Book _book1; private Book _book2; private List _books; private List _remoteBooks; [SetUp] public void Setup() { _book1 = Builder.CreateNew() .With(s => s.ForeignBookId = "1") .Build(); _book2 = Builder.CreateNew() .With(s => s.ForeignBookId = "2") .Build(); _books = new List { _book1, _book2 }; _remoteBooks = _books.JsonClone(); _remoteBooks.ForEach(x => x.Id = 0); var metadata = Builder.CreateNew().Build(); var series = Builder.CreateListOfSize(1).BuildList(); var profile = Builder.CreateNew().Build(); _author = Builder.CreateNew() .With(a => a.Metadata = metadata) .With(a => a.Series = series) .With(a => a.MetadataProfile = profile) .Build(); Mocker.GetMock(MockBehavior.Strict) .Setup(s => s.GetAuthors(new List { _author.Id })) .Returns(new List { _author }); Mocker.GetMock(MockBehavior.Strict) .Setup(s => s.InsertMany(It.IsAny>())); Mocker.GetMock() .Setup(s => s.FilterBooks(It.IsAny(), It.IsAny())) .Returns(_remoteBooks); Mocker.GetMock() .Setup(s => s.GetAuthorInfo(It.IsAny(), true)) .Callback(() => { throw new AuthorNotFoundException(_author.ForeignAuthorId); }); Mocker.GetMock() .Setup(x => x.GetFilesByAuthor(It.IsAny())) .Returns(new List()); Mocker.GetMock() .Setup(x => x.GetByAuthor(It.IsAny(), It.IsAny())) .Returns(new List()); Mocker.GetMock() .Setup(x => x.FindByForeignId(It.IsAny>())) .Returns(new List()); Mocker.GetMock() .Setup(x => x.All()) .Returns(new List()); Mocker.GetMock() .Setup(x => x.ShouldMonitorNewBook(It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(true); } private void GivenNewAuthorInfo(Author author) { Mocker.GetMock() .Setup(s => s.GetAuthorInfo(_author.ForeignAuthorId, true)) .Returns(author); } private void GivenAuthorFiles() { Mocker.GetMock() .Setup(x => x.GetFilesByAuthor(It.IsAny())) .Returns(Builder.CreateListOfSize(1).BuildList()); } private void GivenBooksForRefresh(List books) { Mocker.GetMock(MockBehavior.Strict) .Setup(s => s.GetBooksForRefresh(It.IsAny(), It.IsAny>())) .Returns(books); } private void AllowAuthorUpdate() { Mocker.GetMock(MockBehavior.Strict) .Setup(x => x.UpdateAuthor(It.IsAny())) .Returns((Author a) => a); } [Test] public void should_not_publish_author_updated_event_if_metadata_not_updated() { var newAuthorInfo = _author.JsonClone(); newAuthorInfo.Metadata = _author.Metadata.Value.JsonClone(); newAuthorInfo.Books = _remoteBooks; GivenNewAuthorInfo(newAuthorInfo); GivenBooksForRefresh(_books); AllowAuthorUpdate(); Subject.Execute(new RefreshAuthorCommand(_author.Id)); VerifyEventNotPublished(); VerifyEventPublished(); } [Test] public void should_publish_author_updated_event_if_metadata_updated() { var newAuthorInfo = _author.JsonClone(); newAuthorInfo.Metadata = _author.Metadata.Value.JsonClone(); newAuthorInfo.Metadata.Value.Images = new List { new MediaCover.MediaCover(MediaCover.MediaCoverTypes.Logo, "dummy") }; newAuthorInfo.Books = _remoteBooks; GivenNewAuthorInfo(newAuthorInfo); GivenBooksForRefresh(new List()); AllowAuthorUpdate(); Subject.Execute(new RefreshAuthorCommand(_author.Id)); VerifyEventPublished(); VerifyEventPublished(); } [Test] public void should_call_new_book_monitor_service_when_adding_book() { var newBook = Builder.CreateNew() .With(x => x.Id = 0) .With(x => x.ForeignBookId = "3") .Build(); _remoteBooks.Add(newBook); var newAuthorInfo = _author.JsonClone(); newAuthorInfo.Metadata = _author.Metadata.Value.JsonClone(); newAuthorInfo.Books = _remoteBooks; GivenNewAuthorInfo(newAuthorInfo); GivenBooksForRefresh(_books); AllowAuthorUpdate(); Subject.Execute(new RefreshAuthorCommand(_author.Id)); Mocker.GetMock() .Verify(x => x.ShouldMonitorNewBook(newBook, _books, _author.MonitorNewItems), Times.Once()); } [Test] public void should_log_error_and_delete_if_musicbrainz_id_not_found_and_author_has_no_files() { Mocker.GetMock() .Setup(x => x.DeleteAuthor(It.IsAny(), It.IsAny(), It.IsAny())); Subject.Execute(new RefreshAuthorCommand(_author.Id)); Mocker.GetMock() .Verify(v => v.UpdateAuthor(It.IsAny()), Times.Never()); Mocker.GetMock() .Verify(v => v.DeleteAuthor(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); ExceptionVerification.ExpectedErrors(1); ExceptionVerification.ExpectedWarns(1); } [Test] public void should_log_error_but_not_delete_if_musicbrainz_id_not_found_and_author_has_files() { GivenAuthorFiles(); GivenBooksForRefresh(new List()); Subject.Execute(new RefreshAuthorCommand(_author.Id)); Mocker.GetMock() .Verify(v => v.UpdateAuthor(It.IsAny()), Times.Never()); Mocker.GetMock() .Verify(v => v.DeleteAuthor(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); ExceptionVerification.ExpectedErrors(2); } [Test] public void should_update_if_musicbrainz_id_changed_and_no_clash() { var newAuthorInfo = _author.JsonClone(); newAuthorInfo.Metadata = _author.Metadata.Value.JsonClone(); newAuthorInfo.Books = _remoteBooks; newAuthorInfo.ForeignAuthorId = _author.ForeignAuthorId + 1; newAuthorInfo.Metadata.Value.Id = 100; GivenNewAuthorInfo(newAuthorInfo); var seq = new MockSequence(); Mocker.GetMock(MockBehavior.Strict) .Setup(x => x.FindById(newAuthorInfo.ForeignAuthorId)) .Returns(default(Author)); // Make sure that the author is updated before we refresh the books Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.UpdateAuthor(It.IsAny())) .Returns((Author a) => a); Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.GetBooksForRefresh(It.IsAny(), It.IsAny>())) .Returns(new List()); // Update called twice for a move/merge Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.UpdateAuthor(It.IsAny())) .Returns((Author a) => a); Subject.Execute(new RefreshAuthorCommand(_author.Id)); Mocker.GetMock() .Verify(v => v.UpdateAuthor(It.Is(s => s.AuthorMetadataId == 100 && s.ForeignAuthorId == newAuthorInfo.ForeignAuthorId)), Times.Exactly(2)); } [Test] public void should_merge_if_musicbrainz_id_changed_and_new_id_already_exists() { var existing = _author; var clash = _author.JsonClone(); clash.Id = 100; clash.Metadata = existing.Metadata.Value.JsonClone(); clash.Metadata.Value.Id = 101; clash.Metadata.Value.ForeignAuthorId = clash.Metadata.Value.ForeignAuthorId + 1; Mocker.GetMock(MockBehavior.Strict) .Setup(x => x.FindById(clash.Metadata.Value.ForeignAuthorId)) .Returns(clash); var newAuthorInfo = clash.JsonClone(); newAuthorInfo.Metadata = clash.Metadata.Value.JsonClone(); newAuthorInfo.Books = _remoteBooks; GivenNewAuthorInfo(newAuthorInfo); var seq = new MockSequence(); // Make sure that the author is updated before we refresh the books Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.GetBooksByAuthor(existing.Id)) .Returns(_books); Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.UpdateMany(It.IsAny>())); Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.DeleteAuthor(existing.Id, It.IsAny(), false)); Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.UpdateAuthor(It.Is(a => a.Id == clash.Id))) .Returns((Author a) => a); Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.GetBooksForRefresh(clash.AuthorMetadataId, It.IsAny>())) .Returns(_books); // Update called twice for a move/merge Mocker.GetMock(MockBehavior.Strict) .InSequence(seq) .Setup(x => x.UpdateAuthor(It.IsAny())) .Returns((Author a) => a); Subject.Execute(new RefreshAuthorCommand(_author.Id)); // the retained author gets updated Mocker.GetMock() .Verify(v => v.UpdateAuthor(It.Is(s => s.Id == clash.Id)), Times.Exactly(2)); // the old one gets removed Mocker.GetMock() .Verify(v => v.DeleteAuthor(existing.Id, false, false)); Mocker.GetMock() .Verify(v => v.UpdateMany(It.Is>(x => x.Count == _books.Count))); ExceptionVerification.ExpectedWarns(1); } } }