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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+ CD
+
+
+ 267785
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 2
+
+
+ 267785
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+ The Classic Composers, Volume 3: Musical Masterpieces
+ Promotion
+ normal
+
+ eng
+
+
+ 2005
+ US
+
+
+ 2005
+
+ United States
+ United States
+
+ US
+
+
+
+
+
+
+ 1
+ CD
+
+
+
+
+
+
+
+ Masters of Classical Music, Volume 1
+ Official
+ normal
+ Jewel Case
+
+ eng
+
+
+ 2010-10-18
+ DE
+
+
+ 2010-10-18
+
+ Germany
+ Germany
+
+ DE
+
+
+
+
+ 4006408158011
+
+
+ 1
+ CD
+
+
+
+
+
+
+
+ Les grands compositeurs : Mozart prodige musical
+ Official
+ normal
+ Other
+
+
+
+ 2003
+ FR
+
+
+ 2003
+
+ France
+ France
+
+ FR
+
+
+
+
+
+
+
+ 1
+ CD
+
+
+
+
+
+
+
+ The Classic Composers, Volume 3: Musical Masterpieces
+ Promotion
+ normal
+
+ eng
+
+
+ 2002
+ GB
+
+
+ 2002
+
+ United Kingdom
+ United Kingdom
+
+ GB
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+ Klassiset säveltäjät: Taitoa ja mielikuvitusta
+ normal
+ Other
+
+ fin
+
+
+ FI
+
+
+
+ Finland
+ Finland
+
+ FI
+
+
+
+
+
+
+
+ 1
+ CD
+
+
+
+
+
+
+
+ The Classic Composers, Volume 3: Musical Masterpieces
+ Official
+ normal
+ Other
+
+ eng
+
+
+ DE
+
+
+
+ Germany
+ Germany
+
+ DE
+
+
+
+
+
+
+
+ 1
+ CD
+
+
+
+
+
+
+
+
+
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());
+}