diff --git a/src/devices/cddadevice.cpp b/src/devices/cddadevice.cpp index c2cdb7382..dc7631e07 100644 --- a/src/devices/cddadevice.cpp +++ b/src/devices/cddadevice.cpp @@ -99,6 +99,10 @@ void CddaDevice::Init() { song_count_ = num_tracks; connect(this, SIGNAL(SongsDiscovered(const SongList&)), model_, SLOT(SongsDiscovered(const SongList&))); + // Reset the model before emitting the SongsDiscovered signal. This + // ensures that the model is updated properly even when a disc that + // doesn't exist in MusicBrainz is inserted. + model_->Reset(); emit SongsDiscovered(songs); // Generate MusicBrainz DiscId diff --git a/src/musicbrainz/musicbrainzclient.cpp b/src/musicbrainz/musicbrainzclient.cpp index a82279cc1..4b1d5a566 100644 --- a/src/musicbrainz/musicbrainzclient.cpp +++ b/src/musicbrainz/musicbrainzclient.cpp @@ -34,9 +34,10 @@ const char* MusicBrainzClient::kDiscUrl = const char* MusicBrainzClient::kDateRegex = "^[12]\\d{3}"; const int MusicBrainzClient::kDefaultTimeout = 5000; // msec -MusicBrainzClient::MusicBrainzClient(QObject* parent) +MusicBrainzClient::MusicBrainzClient(QObject* parent, + QNetworkAccessManager* network) : QObject(parent), - network_(new NetworkAccessManager(this)), + network_(network ? network : new NetworkAccessManager(this)), timeouts_(new NetworkTimeouts(kDefaultTimeout, this)) {} void MusicBrainzClient::Start(int id, const QString& mbid) { @@ -69,7 +70,8 @@ void MusicBrainzClient::StartDiscIdRequest(const QString& discid) { QNetworkReply* reply = network_->get(req); NewClosure(reply, SIGNAL(finished()), this, - SLOT(DiscIdRequestFinished(QNetworkReply*)), reply); + SLOT(DiscIdRequestFinished(const QString&, QNetworkReply*)), + discid, reply); timeouts_->AddReply(reply); } @@ -81,7 +83,8 @@ void MusicBrainzClient::CancelAll() { requests_.clear(); } -void MusicBrainzClient::DiscIdRequestFinished(QNetworkReply* reply) { +void MusicBrainzClient::DiscIdRequestFinished(const QString& discid, + QNetworkReply* reply) { reply->deleteLater(); ResultList ret; @@ -98,6 +101,8 @@ void MusicBrainzClient::DiscIdRequestFinished(QNetworkReply* reply) { // -get title // -get artist // -get all the tracks' tags + // Note: If there are multiple releases for the discid, the first + // release is chosen. QXmlStreamReader reader(reply); while (!reader.atEnd()) { QXmlStreamReader::TokenType type = reader.readNext(); @@ -105,9 +110,9 @@ void MusicBrainzClient::DiscIdRequestFinished(QNetworkReply* reply) { QStringRef name = reader.name(); if (name == "title") { album = reader.readElementText(); - } else if (name == "artist") { + } else if (name == "artist-credit") { ParseArtist(&reader, &artist); - } else if (name == "track-list") { + } else if (name == "medium-list") { break; } } @@ -115,16 +120,20 @@ void MusicBrainzClient::DiscIdRequestFinished(QNetworkReply* reply) { while (!reader.atEnd()) { QXmlStreamReader::TokenType token = reader.readNext(); - if (token == QXmlStreamReader::StartElement && - reader.name() == "recording") { - ResultList tracks = ParseTrack(&reader); - for (const Result& track : tracks) { - if (!track.title_.isEmpty()) { - ret << track; + if (token == QXmlStreamReader::StartElement && reader.name() == "medium") { + // Get the medium with a matching discid. + if (MediumHasDiscid(discid, &reader)) { + ResultList tracks = ParseMedium(&reader); + for (const Result& track : tracks) { + if (!track.title_.isEmpty()) { + ret << track; + } } + } else { + Utilities::ConsumeCurrentElement(&reader); } } else if (token == QXmlStreamReader::EndElement && - reader.name() == "track-list") { + reader.name() == "medium-list") { break; } } @@ -159,6 +168,72 @@ void MusicBrainzClient::RequestFinished(QNetworkReply* reply, int id) { emit Finished(id, UniqueResults(ret)); } +bool MusicBrainzClient::MediumHasDiscid(const QString& discid, + QXmlStreamReader* reader) { + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement && reader->name() == "disc" && + reader->attributes().value("id").toString() == discid) { + return true; + } else if (type == QXmlStreamReader::EndElement && + reader->name() == "disc-list") { + return false; + } + } + qLog(Debug) << "Reached end of xml stream without encountering "; + return false; +} + +MusicBrainzClient::ResultList MusicBrainzClient::ParseMedium( + QXmlStreamReader* reader) { + ResultList ret; + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + if (reader->name() == "track") { + Result result; + result = ParseTrackFromDisc(reader); + ret << result; + } + } + + if (type == QXmlStreamReader::EndElement && + reader->name() == "track-list") { + break; + } + } + + return ret; +} + +MusicBrainzClient::Result MusicBrainzClient::ParseTrackFromDisc( + QXmlStreamReader* reader) { + Result result; + + while (!reader->atEnd()) { + QXmlStreamReader::TokenType type = reader->readNext(); + + if (type == QXmlStreamReader::StartElement) { + QStringRef name = reader->name(); + if (name == "position") { + result.track_ = reader->readElementText().toInt(); + } else if (name == "length") { + result.duration_msec_ = reader->readElementText().toInt(); + } else if (name == "title") { + result.title_ = reader->readElementText(); + } + } + + if (type == QXmlStreamReader::EndElement && reader->name() == "track") { + break; + } + } + + return result; +} + MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack( QXmlStreamReader* reader) { Result result; @@ -174,7 +249,7 @@ MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack( result.title_ = reader->readElementText(); } else if (name == "length") { result.duration_msec_ = reader->readElementText().toInt(); - } else if (name == "artist") { + } else if (name == "artist-credit") { ParseArtist(reader, &result.artist_); } else if (name == "release") { releases << ParseRelease(reader); @@ -197,15 +272,24 @@ MusicBrainzClient::ResultList MusicBrainzClient::ParseTrack( return ret; } +// Parse the artist. Multiple artists are joined together with the +// joinphrase from musicbrainz. void MusicBrainzClient::ParseArtist(QXmlStreamReader* reader, QString* artist) { + QString join_phrase; while (!reader->atEnd()) { QXmlStreamReader::TokenType type = reader->readNext(); - if (type == QXmlStreamReader::StartElement && reader->name() == "name") { - *artist = reader->readElementText(); + if (type == QXmlStreamReader::StartElement && + reader->name() == "name-credit") { + join_phrase = reader->attributes().value("joinphrase").toString(); } - if (type == QXmlStreamReader::EndElement && reader->name() == "artist") { + if (type == QXmlStreamReader::StartElement && reader->name() == "name") { + *artist += reader->readElementText() + join_phrase; + } + + if (type == QXmlStreamReader::EndElement && + reader->name() == "artist-credit") { return; } } diff --git a/src/musicbrainz/musicbrainzclient.h b/src/musicbrainz/musicbrainzclient.h index cdaf20094..e17065343 100644 --- a/src/musicbrainz/musicbrainzclient.h +++ b/src/musicbrainz/musicbrainzclient.h @@ -38,7 +38,11 @@ class MusicBrainzClient : public QObject { // the Finished signal - they have no meaning to MusicBrainzClient. public: - MusicBrainzClient(QObject* parent = nullptr); + // The second argument allows for specifying a custom network access + // manager. It is used in tests. The ownership of network + // is not transferred. + MusicBrainzClient(QObject* parent = nullptr, + QNetworkAccessManager* network = nullptr); struct Result { Result() : duration_msec_(0), track_(0), year_(-1) {} @@ -94,7 +98,7 @@ signals: private slots: void RequestFinished(QNetworkReply* reply, int id); - void DiscIdRequestFinished(QNetworkReply* reply); + void DiscIdRequestFinished(const QString& discid, QNetworkReply* reply); private: struct Release { @@ -113,6 +117,9 @@ signals: int year_; }; + static bool MediumHasDiscid(const QString& discid, QXmlStreamReader* reader); + static ResultList ParseMedium(QXmlStreamReader* reader); + static Result ParseTrackFromDisc(QXmlStreamReader* reader); static ResultList ParseTrack(QXmlStreamReader* reader); static void ParseArtist(QXmlStreamReader* reader, QString* artist); static Release ParseRelease(QXmlStreamReader* reader); @@ -133,5 +140,4 @@ inline uint qHash(const MusicBrainzClient::Result& result) { return qHash(result.album_) ^ qHash(result.artist_) ^ result.duration_msec_ ^ qHash(result.title_) ^ result.track_ ^ result.year_; } - #endif // MUSICBRAINZCLIENT_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9149e4b1f..f7d0a42ac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -131,6 +131,7 @@ add_test_file(fmpsparser_test.cpp false) #add_test_file(librarymodel_test.cpp true) #add_test_file(m3uparser_test.cpp false) add_test_file(mergedproxymodel_test.cpp false) +add_test_file(musicbrainzclient_test.cpp false) add_test_file(organiseformat_test.cpp false) add_test_file(organisedialog_test.cpp false) #add_test_file(playlist_test.cpp true) diff --git a/tests/data/discid_2cd.xml b/tests/data/discid_2cd.xml new file mode 100644 index 000000000..e234b7d9c --- /dev/null +++ b/tests/data/discid_2cd.xml @@ -0,0 +1,399 @@ + + + + 267785 + + + Live on the Edge of Forever + Official + normal + + eng + + + + + + Symphony X + Symphony X + + + + 2001-11-13 + US + + + 2001-11-13 + + United States + United States + + US + + + + + + false + 0 + false + false + + + + 1 + CD + + + 213610 + + + + + 1 + 1 + 98266 + + Prelude + 98266 + + + + 2 + 2 + 318400 + + Evolution (The Grand Design) + 318400 + + + + 3 + 3 + 390826 + + Fallen / Transcendence + 390826 + + + + 4 + 4 + 459040 + + Communion and the Oracle + 459040 + + + + 5 + 5 + 219760 + + The Bird-Serpent War + 219760 + + + + 6 + 6 + 309573 + + On the Breath of Poseidon + 309573 + + + + 7 + 7 + 425226 + + Egypt + 425226 + + + + 8 + 8 + 352666 + + The Death of Balance / Candlelight Fantasia + 352666 + + + + 9 + 9 + 272373 + + The Eyes of Medusa + 272373 + + + + + + 2 + CD + + + 267785 + + + + + 1 + 1 + 394600 + + Smoke and Mirrors + 394600 + + + + 2 + 2 + 441866 + + Church of the Machine + 441866 + + + + 3 + 3 + 849426 + + Through the Looking Glass + 849426 + + + + 4 + 4 + 442773 + + Of Sins and Shadows + 442773 + + + + 5 + 5 + 245160 + + Sea of Lies + 245160 + + + + 6 + 6 + 1194640 + + The Divine Wings of Tragedy + 1194640 + + + + + + + + Live on the Edge of Forever + Official + normal + + eng + + + + + + Symphony X + Symphony X + + + + 2001-10-22 + DE + + + 2001-10-22 + + Germany + Germany + + DE + + + + + B00005Q8VB + + false + 0 + false + false + + + + 1 + + + 213610 + + + + + 1 + 1 + 98266 + + Prelude + 98266 + + + + 2 + 2 + 318400 + + Evolution (The Grand Design) + 318400 + + + + 3 + 3 + 390826 + + Fallen / Transcendence + 390826 + + + + 4 + 4 + 459040 + + Communion and the Oracle + 459040 + + + + 5 + 5 + 219760 + + The Bird-Serpent War + 219760 + + + + 6 + 6 + 309573 + + On the Breath of Poseidon + 309573 + + + + 7 + 7 + 425226 + + Egypt + 425226 + + + + 8 + 8 + 352666 + + The Death of Balance / Candlelight Fantasia + 352666 + + + + 9 + 9 + 272373 + + The Eyes of Medusa + 272373 + + + + + + 2 + + + 267785 + + + + + 1 + 1 + 394600 + + Smoke and Mirrors + 394600 + + + + 2 + 2 + 441866 + + Church of the Machine + 441866 + + + + 3 + 3 + 849426 + + Through the Looking Glass + 849426 + + + + 4 + 4 + 442773 + + Of Sins and Shadows + 442773 + + + + 5 + 5 + 245160 + + Sea of Lies + 245160 + + + + 6 + 6 + 1194640 + + The Divine Wings of Tragedy + 1194640 + + + + + + + + + diff --git a/tests/data/recording.xml b/tests/data/recording.xml new file mode 100644 index 000000000..cd7ee1b13 --- /dev/null +++ b/tests/data/recording.xml @@ -0,0 +1,48 @@ + + + + Victoria und ihr Husar: Pardon Madame + 203906 + + + + Paul Abraham + Abraham, Paul + + + + + + An Evening at the Operetta + Official + normal + Jewel Case + + deu + + + 1992 + + + 1992 + + + 8712157906266 + + + 1 + CD + + + 6 + 6 + Victoria und ihr Husar: Pardon Madame + 203906 + + + + + + + + diff --git a/tests/data/recording_with_multiple_releases.xml b/tests/data/recording_with_multiple_releases.xml new file mode 100644 index 000000000..da34590f1 --- /dev/null +++ b/tests/data/recording_with_multiple_releases.xml @@ -0,0 +1,285 @@ + + + + Symphony no. 40 in G minor, K. 550 "Great": I. Allegro molto + 458386 + + + + Hans Graf + Graf, Hans + + + + + Mozarteum Orchester Salzburg + Mozarteum Orchester Salzburg + + + + + + Masters of Classical Music, Volume 1 + Official + normal + Jewel Case + + eng + + + 1988 + US + + + 1988 + + United States + United States + + US + + + + + 018111580120 + + + 1 + CD + + + 4 + 4 + Symphony no. 40 in G minor, K. 550 "Great": I. Allegro molto + 458960 + + + + + + + The Classic Composers, Volume 3: Musical Masterpieces + Promotion + normal + + eng + + + 2005 + US + + + 2005 + + United States + United States + + US + + + + + + + 1 + CD + + + 9 + 9 + Symphony No. 40 in G minor with clarinets, K. 550: I. Allegro molto + 458386 + + + + + + + Masters of Classical Music, Volume 1 + Official + normal + Jewel Case + + eng + + + 2010-10-18 + DE + + + 2010-10-18 + + Germany + Germany + + DE + + + + + 4006408158011 + + + 1 + CD + + + 4 + 4 + Symphony no. 40 in G minor, K. 550 "Great": I. Allegro molto + 458960 + + + + + + + Les grands compositeurs : Mozart prodige musical + Official + normal + Other + + + + 2003 + FR + + + 2003 + + France + France + + FR + + + + + + + + 1 + CD + + + 9 + 9 + Symphonie n° 40, K 550 : 1er mouvement + 456000 + + + + + + + The Classic Composers, Volume 3: Musical Masterpieces + Promotion + normal + + eng + + + 2002 + GB + + + 2002 + + United Kingdom + United Kingdom + + GB + + + + + + + 1 + + + 9 + 9 + Symphony No. 40 in G minor with clarinets, K. 550: I. Allegro molto + 458386 + + + + + + + Klassiset säveltäjät: Taitoa ja mielikuvitusta + normal + Other + + fin + + + FI + + + + Finland + Finland + + FI + + + + + + + + 1 + CD + + + 9 + 9 + Sinfonia nro 40 G-molli, KV 550: 1. osa + 458386 + + + + + + + The Classic Composers, Volume 3: Musical Masterpieces + Official + normal + Other + + eng + + + DE + + + + Germany + Germany + + DE + + + + + + + + 1 + CD + + + 9 + 9 + Symphony no. 40 in G minor, K. 550 "Great": I. Allegro molto + 458000 + + + + + + + + diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc index 42716808b..235240b04 100644 --- a/tests/data/testdata.qrc +++ b/tests/data/testdata.qrc @@ -8,6 +8,7 @@ beep.wma beep.m4a brokensong.cue + discid_2cd.xml fmpsplaycount.mp3 fmpsplaycountboth.mp3 fmpsplaycountuser.mp3 @@ -22,6 +23,8 @@ popmrating.mp3 pls_one.pls pls_somafm.pls + recording.xml + recording_with_multiple_releases.xml secretagent.asx secretagent.pls test.m3u diff --git a/tests/musicbrainzclient_test.cpp b/tests/musicbrainzclient_test.cpp new file mode 100644 index 000000000..52fc5c7c7 --- /dev/null +++ b/tests/musicbrainzclient_test.cpp @@ -0,0 +1,250 @@ +/* This file is part of Clementine. + Copyright 2010, David Sansome + + Clementine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Clementine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Clementine. If not, see . +*/ + +#include + +#include "core/logging.h" +#include "musicbrainz/musicbrainzclient.h" + +#include +#include +#include +#include +#include +#include + +#include "mock_networkaccessmanager.h" +#include "gtest/gtest.h" +#include "test_utils.h" + +namespace { +typedef QList ResultList; +Q_DECLARE_METATYPE(ResultList); +}; + +class MusicBrainzClientTest : public ::testing::Test { + protected: + static void SetUpTestCase() { + qRegisterMetaType("MusicBrainzClient::ResultList"); + } + + void SetUp() { + mock_network_.reset(new MockNetworkAccessManager); + } + + // Reads the data from a file into a QByteArray and returns it. + QByteArray ReadDataFromFile(const QString& filename) { + QFile file(filename); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + return data; + } + + std::unique_ptr mock_network_; +}; + + +// Test if a discid that do not exist in the musicbrainz database +// generates an empty result. +TEST_F(MusicBrainzClientTest, DiscIdNotFound) { + QByteArray data = + "Not " + "FoundFor usage, please see: " + "http://musicbrainz.org/development/mmd"; + + // Create a MusicBrainzClient instance with mock_network_. + MusicBrainzClient musicbrainz_client(nullptr, mock_network_.get()); + + // Hook the data as the response to a query of a given type. + QMap params; + params["inc"] = "artists+recordings"; + MockNetworkReply* discid_reply = + mock_network_->ExpectGet("discid", params, 200, data); + + // Set up a QSignalSpy which stores the result. + QSignalSpy spy(&musicbrainz_client, + SIGNAL(Finished(const QString&, const QString, + const MusicBrainzClient::ResultList&))); + ASSERT_TRUE(spy.isValid()); + EXPECT_EQ(0, spy.count()); + + // Start the request and get a result. The argument doesn't matter + // in the test. + musicbrainz_client.StartDiscIdRequest("fooDiscid"); + discid_reply->Done(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + EXPECT_EQ(1, spy.count()); + + QList result = spy.takeFirst(); + QString artist = result.takeFirst().toString(); + QString album = result.takeFirst().toString(); + ResultList tracks = result.takeFirst().value(); + + // Check that title and artist are empty, and that there are zero tracks. + EXPECT_TRUE(artist.isEmpty()); + EXPECT_TRUE(album.isEmpty()); + EXPECT_EQ(0, tracks.count()); +} + +// Test if MusicBrainzClient::StartDiscIdRequest() parses a discid +// correctly. +TEST_F(MusicBrainzClientTest, ParseDiscID) { + QByteArray data = ReadDataFromFile(":testdata/discid_2cd.xml"); + ASSERT_FALSE(data.isEmpty()); + + // The following are the expected values given for the test file + // discid_2cd.xml. The discid corresponds to the 2nd disc in the + // set. The test file contains two releases but we only parse the first. + const QString expected_artist = "Symphony X"; + const QString expected_title = "Live on the Edge of Forever"; + const int expected_number_of_tracks = 6; + + // Create a MusicBrainzClient instance with mock_network_. + MusicBrainzClient musicbrainz_client(nullptr, mock_network_.get()); + + // Hook the data as the response to a query of a given type. + QMap params; + params["inc"] = "artists+recordings"; + MockNetworkReply* discid_reply = + mock_network_->ExpectGet("discid", params, 200, data); + + // Set up a QSignalSpy which stores the result. + QSignalSpy spy(&musicbrainz_client, + SIGNAL(Finished(const QString&, const QString, + const MusicBrainzClient::ResultList&))); + ASSERT_TRUE(spy.isValid()); + EXPECT_EQ(0, spy.count()); + + // Start the request and get a result. The argument doesn't matter + // in the test. It is here set to the discid of the requested disc. + musicbrainz_client.StartDiscIdRequest("lvcH9_vbw_rJAbXieTOo1CbyNmQ-"); + discid_reply->Done(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + EXPECT_EQ(1, spy.count()); + + QList result = spy.takeFirst(); + QString artist = result.takeFirst().toString(); + QString album = result.takeFirst().toString(); + ResultList tracks = result.takeFirst().value(); + + // Check that title and artist are correct. + EXPECT_EQ(expected_artist, artist); + EXPECT_EQ(expected_title, album); + + // Check that we get the correct number of tracks, i.e. that the + // correct disc is chosen in a multi-disc release. + EXPECT_EQ(expected_number_of_tracks, tracks.count()); + + // Check that the tracks is ordered by track number in ascending + // order. + for (int i = 0; i < tracks.count(); ++i) { + EXPECT_EQ(i + 1, tracks[i].track_); + } + + // Check some track information. + EXPECT_EQ("Smoke and Mirrors", tracks[0].title_); + EXPECT_EQ(1, tracks[0].track_); + EXPECT_EQ(394600, tracks[0].duration_msec_); + + EXPECT_EQ("Church of the Machine", tracks[1].title_); + EXPECT_EQ(2, tracks[1].track_); + EXPECT_EQ(441866, tracks[1].duration_msec_); +} + +// Test if MusicBrainzClient::Start() parses a track correctly. +TEST_F(MusicBrainzClientTest, ParseTrack) { + QByteArray data = ReadDataFromFile(":testdata/recording.xml"); + ASSERT_FALSE(data.isEmpty()); + + // Expected results from the test file recording.xml: + const int expected_track_number = 6; + const QString expected_title = "Victoria und ihr Husar: Pardon Madame"; + const QString expected_artist = "Paul Abraham"; + const QString expected_album = "An Evening at the Operetta"; + + // Create a MusicBrainzClient instance with mock_network_. + MusicBrainzClient musicbrainz_client(nullptr, mock_network_.get()); + + // Hook the data as the response to a query of a given type. + QMap params; + params["inc"] = "artists+releases+media"; + MockNetworkReply* discid_reply = + mock_network_->ExpectGet("recording", params, 200, data); + + QSignalSpy spy(&musicbrainz_client, + SIGNAL(Finished(int, const MusicBrainzClient::ResultList&))); + ASSERT_TRUE(spy.isValid()); + EXPECT_EQ(0, spy.count()); + + // Start the request and get a result. + // The mbid argument doesn't matter in the test. + const int sent_id = 0; + musicbrainz_client.Start(sent_id, "fooMbid"); + discid_reply->Done(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + EXPECT_EQ(1, spy.count()); + + QList result = spy.takeFirst(); + int id = result.takeFirst().toInt(); + EXPECT_EQ(sent_id, id); + + ResultList tracks = result.takeFirst().value(); + for (const MusicBrainzClient::Result& track : tracks) { + EXPECT_EQ(expected_track_number, track.track_); + EXPECT_EQ(expected_title, track.title_); + EXPECT_EQ(expected_artist, track.artist_); + EXPECT_EQ(expected_album, track.album_); + } +} + +// For a recording with multiple releases, we should get them all. +TEST_F(MusicBrainzClientTest, ParseTrackWithMultipleReleases) { + QByteArray data = + ReadDataFromFile(":testdata/recording_with_multiple_releases.xml"); + ASSERT_FALSE(data.isEmpty()); + + const int expected_number_of_releases = 7; + + // Create a MusicBrainzClient instance with mock_network_. + MusicBrainzClient musicbrainz_client(nullptr, mock_network_.get()); + + // Hook the data as the response to a query of a given type. + QMap params; + params["inc"] = "artists+releases+media"; + MockNetworkReply* discid_reply = + mock_network_->ExpectGet("recording", params, 200, data); + + QSignalSpy spy(&musicbrainz_client, + SIGNAL(Finished(int, const MusicBrainzClient::ResultList&))); + ASSERT_TRUE(spy.isValid()); + EXPECT_EQ(0, spy.count()); + + // Start the request and get a result. + // The mbid argument doesn't matter in the test. + const int sent_id = 0; + musicbrainz_client.Start(sent_id, "fooMbid"); + discid_reply->Done(); + QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + EXPECT_EQ(1, spy.count()); + + QList result = spy.takeFirst(); + int id = result.takeFirst().toInt(); + EXPECT_EQ(sent_id, id); + + ResultList tracks = result.takeFirst().value(); + EXPECT_EQ(expected_number_of_releases, tracks.count()); +}