1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "base/logging.h"
6#include "chrome/common/media_galleries/itunes_library.h"
7#include "chrome/utility/media_galleries/itunes_library_parser.h"
8#include "testing/gtest/include/gtest/gtest.h"
9
10#define SIMPLE_HEADER()         \
11    "<plist>"                   \
12    "  <dict>"                  \
13    "    <key>Tracks</key>"     \
14    "    <dict>"
15
16#define SIMPLE_TRACK(key, id, path, artist, album)                     \
17    "<key>" #key "</key>"                                              \
18    "<dict>"                                                           \
19    "  <key>Track ID</key><integer>" #id "</integer>"                  \
20    "  <key>Location</key><string>file://localhost/" path "</string>"  \
21    "  <key>Artist</key><string>" artist "</string>"                   \
22    "  <key>Album</key><string>" album "</string>"                     \
23    "</dict>"
24
25#define SIMPLE_FOOTER()  \
26    "    </dict>"        \
27    "  </dict>"          \
28    "</plist>"
29
30namespace itunes {
31
32namespace {
33
34void CompareTrack(const parser::Track& a, const parser::Track& b) {
35  EXPECT_EQ(a.id, b.id);
36  EXPECT_EQ(a.location.value(), b.location.value());
37}
38
39void CompareAlbum(const parser::Album& a, const parser::Album& b) {
40  EXPECT_EQ(a.size(), b.size());
41
42  parser::Album::const_iterator a_it;
43  parser::Album::const_iterator b_it;
44  for (a_it = a.begin(), b_it = b.begin();
45       a_it != a.end() && b_it != b.end();
46       ++a_it, ++b_it) {
47    CompareTrack(*a_it, *b_it);
48  }
49}
50
51void CompareAlbums(const parser::Albums& a, const parser::Albums& b) {
52  EXPECT_EQ(a.size(), b.size());
53
54  parser::Albums::const_iterator a_it;
55  parser::Albums::const_iterator b_it;
56  for (a_it = a.begin(), b_it = b.begin();
57       a_it != a.end() && b_it != b.end();
58       ++a_it, ++b_it) {
59    EXPECT_EQ(a_it->first, b_it->first);
60    CompareAlbum(a_it->second, b_it->second);
61  }
62}
63
64void CompareLibrary(const parser::Library& a, const parser::Library& b) {
65  EXPECT_EQ(a.size(), b.size());
66
67  parser::Library::const_iterator a_it;
68  parser::Library::const_iterator b_it;
69  for (a_it = a.begin(), b_it = b.begin();
70       a_it != a.end() && b_it != b.end();
71       ++a_it, ++b_it) {
72    EXPECT_EQ(a_it->first, b_it->first);
73    CompareAlbums(a_it->second, b_it->second);
74  }
75}
76
77class ITunesLibraryParserTest : public testing::Test {
78 public:
79  ITunesLibraryParserTest() {}
80
81  void TestParser(bool expected_result, const std::string& xml) {
82    ITunesLibraryParser parser;
83
84    EXPECT_EQ(expected_result, parser.Parse(xml));
85    if (!expected_result)
86      return;
87
88    CompareLibrary(expected_library_, parser.library());
89  }
90
91  void AddExpectedTrack(uint32 id, const std::string& location,
92                        const std::string& artist, const std::string& album) {
93    // On Mac this pretends that C: is a directory.
94#if defined(OS_MACOSX)
95    std::string os_location = "/" + location;
96#else
97    const std::string& os_location = location;
98#endif
99    parser::Track track(id, base::FilePath::FromUTF8Unsafe(os_location));
100    expected_library_[artist][album].insert(track);
101  }
102
103 private:
104  parser::Library expected_library_;
105
106  DISALLOW_COPY_AND_ASSIGN(ITunesLibraryParserTest);
107};
108
109TEST_F(ITunesLibraryParserTest, EmptyLibrary) {
110  TestParser(false, "");
111}
112
113TEST_F(ITunesLibraryParserTest, MinimalXML) {
114  AddExpectedTrack(1, "C:/dir/Song With Space.mp3", "Artist A", "Album A");
115  TestParser(
116      true,
117      SIMPLE_HEADER()
118      SIMPLE_TRACK(1, 1, "C:/dir/Song%20With%20Space.mp3", "Artist A",
119                   "Album A")
120      SIMPLE_FOOTER());
121}
122
123TEST_F(ITunesLibraryParserTest, MultipleSongs) {
124  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A");
125  AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Artist A", "Album A");
126  AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Album A");
127  AddExpectedTrack(4, "C:/dir/SongB1.mp3", "Artist A", "Album B");
128  AddExpectedTrack(5, "C:/dir/SongB2.mp3", "Artist A", "Album B");
129  AddExpectedTrack(6, "C:/dir2/SongB1.mp3", "Artist B", "Album B");
130  AddExpectedTrack(7, "C:/dir2/SongB2.mp3", "Artist B", "Album B");
131  TestParser(
132      true,
133      SIMPLE_HEADER()
134      SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A")
135      SIMPLE_TRACK(2, 2, "C:/dir/SongA2.mp3", "Artist A", "Album A")
136      SIMPLE_TRACK(3, 3, "C:/dir/SongA3.mp3", "Artist A", "Album A")
137      SIMPLE_TRACK(4, 4, "C:/dir/SongB1.mp3", "Artist A", "Album B")
138      SIMPLE_TRACK(5, 5, "C:/dir/SongB2.mp3", "Artist A", "Album B")
139      SIMPLE_TRACK(6, 6, "C:/dir2/SongB1.mp3", "Artist B", "Album B")
140      SIMPLE_TRACK(7, 7, "C:/dir2/SongB2.mp3", "Artist B", "Album B")
141      SIMPLE_FOOTER());
142}
143
144TEST_F(ITunesLibraryParserTest, MismatchedId) {
145  TestParser(
146      false,
147      SIMPLE_HEADER()
148      SIMPLE_TRACK(1, 2, "C:/dir/SongA1.mp3", "Artist A", "Album A")
149      SIMPLE_FOOTER());
150
151  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A");
152  TestParser(
153      true,
154      SIMPLE_HEADER()
155      SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A")
156      SIMPLE_TRACK(2, 3, "C:/dir/SongA2.mp3", "Artist A", "Album A")
157      SIMPLE_FOOTER());
158}
159
160TEST_F(ITunesLibraryParserTest, OtherDictionaryEntries) {
161  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A");
162  TestParser(
163      true,
164      "<plist>"
165      "  <dict>"
166      "    <key>Other section</key>"
167      "    <dict>"
168      // In Other section, not Tracks.
169      SIMPLE_TRACK(10, 10, "C:/dir/SongB2.mp3", "Artist B", "Album B")
170      "    </dict>"
171      "    <key>Tracks</key>"
172      "    <dict>"
173      "      <key>1</key>"
174      "      <dict>"
175      // In the body of a track dictionary before the interesting entries.
176      SIMPLE_TRACK(20, 20, "C:/dir/SongB2.mp3", "Artist B", "Album B")
177      // Entries in a different order.
178      "        <key>Artist</key><string>Artist A</string>"
179      "        <key>Location</key>"
180      "          <string>file://localhost/C:/dir/SongA1.mp3</string>"
181      "        <key>Album</key><string>Album A</string>"
182      "        <key>Track ID</key><integer>1</integer>"
183      // In the body of a track dictionary after the interesting entries.
184      SIMPLE_TRACK(30, 30, "C:/dir/SongB3.mp3", "Artist B", "Album B")
185      "      </dict>"
186      "      <key>40</key>"
187      "      <dict>"
188      // Missing album name.
189      "        <key>Artist</key><string>Artist B</string>"
190      "        <key>Location</key>"
191      "          <string>file://localhost/C:/dir/SongB4.mp3</string>"
192      "        <key>Track ID</key><integer>1</integer>"
193      "      </dict>"
194      SIMPLE_FOOTER());
195}
196
197TEST_F(ITunesLibraryParserTest, MissingEntry) {
198  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Album A");
199  AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Album A");
200  TestParser(
201      true,
202      SIMPLE_HEADER()
203      SIMPLE_TRACK(1, 1, "C:/dir/SongA1.mp3", "Artist A", "Album A")
204      "<key>2</key><dict>"
205      "  <key>Track ID</key><integer>2</integer>"
206      "  <key>Album</key><string>Album A</string>"
207      "  <key>Foo</key><string>Bar</string>"
208      "   "  // A whitespace node is important for the test.
209      "</dict>"
210      SIMPLE_TRACK(3, 3, "C:/dir/SongA3.mp3", "Artist A", "Album A")
211      SIMPLE_FOOTER());
212}
213
214TEST_F(ITunesLibraryParserTest, UnknownAlbumOrArtist) {
215  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Unknown Album");
216  AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Unknown Artist", "Album A");
217  AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Unknown Artist", "Unknown Album");
218  TestParser(
219      true,
220      SIMPLE_HEADER()
221      "<key>1</key><dict>"
222      "  <key>Track ID</key><integer>1</integer>"
223      "  <key>Location</key><string>file://localhost/C:/dir/SongA1.mp3</string>"
224      "  <key>Artist</key><string>Artist A</string>"
225      "</dict>"
226      "<key>2</key><dict>"
227      "  <key>Track ID</key><integer>2</integer>"
228      "  <key>Location</key><string>file://localhost/C:/dir/SongA2.mp3</string>"
229      "  <key>Album</key><string>Album A</string>"
230      "</dict>"
231      "<key>3</key><dict>"
232      "  <key>Track ID</key><integer>3</integer>"
233      "  <key>Location</key><string>file://localhost/C:/dir/SongA3.mp3</string>"
234      "</dict>"
235      SIMPLE_FOOTER());
236}
237
238TEST_F(ITunesLibraryParserTest, AlbumArtist) {
239  AddExpectedTrack(1, "C:/dir/SongA1.mp3", "Artist A", "Unknown Album");
240  AddExpectedTrack(2, "C:/dir/SongA2.mp3", "Artist A", "Unknown Album");
241  AddExpectedTrack(3, "C:/dir/SongA3.mp3", "Artist A", "Unknown Album");
242  AddExpectedTrack(4, "C:/dir/SongA4.mp3", "Artist A", "Album");
243  TestParser(
244      true,
245      SIMPLE_HEADER()
246      "<key>1</key><dict>"
247      "  <key>Track ID</key><integer>1</integer>"
248      "  <key>Location</key><string>file://localhost/C:/dir/SongA1.mp3</string>"
249      "  <key>Album Artist</key><string>Artist A</string>"
250      "</dict>"
251      "<key>2</key><dict>"
252      "  <key>Track ID</key><integer>2</integer>"
253      "  <key>Location</key><string>file://localhost/C:/dir/SongA2.mp3</string>"
254      "  <key>Artist</key><string>Artist B</string>"
255      "  <key>Album Artist</key><string>Artist A</string>"
256      "</dict>"
257      "<key>3</key><dict>"
258      "  <key>Track ID</key><integer>3</integer>"
259      "  <key>Location</key><string>file://localhost/C:/dir/SongA3.mp3</string>"
260      "  <key>Album Artist</key><string>Artist A</string>"
261      "  <key>Artist</key><string>Artist B</string>"
262      "</dict>"
263      "<key>4</key><dict>"
264      "  <key>Track ID</key><integer>4</integer>"
265      "  <key>Location</key><string>file://localhost/C:/dir/SongA4.mp3</string>"
266      "  <key>Album</key><string>Album</string>"
267      "  <key>Artist</key><string>Artist B</string>"
268      "  <key>Album Artist</key><string>Artist A</string>"
269      "</dict>"
270      SIMPLE_FOOTER());
271}
272
273TEST_F(ITunesLibraryParserTest, MacPath) {
274  AddExpectedTrack(1, "dir/Song With Space.mp3", "Artist A", "Album A");
275  TestParser(
276      true,
277      SIMPLE_HEADER()
278      // This path is concatenated with "http://localhost/", so no leading
279      // slash should be used.
280      SIMPLE_TRACK(1, 1, "dir/Song%20With%20Space.mp3", "Artist A", "Album A")
281      SIMPLE_FOOTER());
282}
283
284}  // namespace
285
286}  // namespace itunes
287