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/iphoto_library.h"
7#include "chrome/utility/media_galleries/iphoto_library_parser.h"
8#include "testing/gtest/include/gtest/gtest.h"
9
10#define SIMPLE_HEADER()                          \
11    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
12    "<plist version=\"1.0\">"                    \
13    "  <dict>"                                   \
14    "    <key>Archive Path</key>"                \
15    "    <string>/Users/username/px</string>"
16
17#define ALBUMS_HEADER()             \
18    "    <key>List of Albums</key>" \
19    "    <array>"
20
21#define ALBUMS_FOOTER() \
22    "    </array>"
23
24#define SIMPLE_ALBUM(id, name, photo1, photo2)         \
25    "    <dict>"                                       \
26    "      <key>AlbumId</key>"                         \
27    "      <integer>" #id "</integer>"                 \
28    "      <key>AlbumName</key>"                       \
29    "      <string>" name "</string>"                  \
30    "      <key>KeyList</key>"                         \
31    "      <array>"                                    \
32    "      <string>" #photo1 "</string>"               \
33    "      <string>" #photo2 "</string>"               \
34    "      </array>"                                   \
35    "    </dict>"
36
37#define IMAGE_LIST_HEADER()           \
38    "   <key>Master Image List</key>" \
39    "   <dict>"
40
41#define IMAGE_LIST_FOOTER() \
42    "   </dict>"
43
44#define SIMPLE_PHOTO(id, guid, path, caption) \
45    "  <key>" #id "</key>"                    \
46    "  <dict>"                                \
47    "    <key>MediaType</key>"                \
48    "    <string>Image</string>"              \
49    "    <key>Caption</key>"                  \
50    "    <string>" caption "</string>"        \
51    "    <key>GUID</key>"                     \
52    "    <string>" #guid "</string>"          \
53    "    <key>ModDateAsTimerInterval</key>"   \
54    "    <string>386221543.0000</string>"     \
55    "    <key>DateAsTimerInterval</key>"      \
56    "    <string>386221543.0000</string>"     \
57    "    <key>DateAsTimerIntervalGMT</key>"   \
58    "    <string>385123456.00</string>"       \
59    "    <key>ImagePath</key>"                \
60    "    <string>" path "</string>"           \
61    "    <key>OriginalPath</key>"             \
62    "    <string>/original" path "</string>"  \
63    "    <key>ThumbPath</key>"                \
64    "    <string>" path "</string>"           \
65    "  </dict>"
66
67#define SIMPLE_FOOTER()  \
68    "  </dict>"          \
69    "</plist>"
70
71 // Mismatched key/string tag at ImagePath.
72#define MALFORMED_PHOTO1(id, guid, path, caption) \
73    "  <key>" #id "</key>"             \
74    "  <dict>"                         \
75    "    <key>MediaType</key>"         \
76    "    <string>Image</string>"       \
77    "    <key>Caption<key>"            \
78    "    <string>" caption "</string>" \
79    "    <key>GUID</key>"              \
80    "    <string>" #guid "</string>"   \
81    "    <key>ImagePath</string>"      \
82    "    <string>" path "</string>"    \
83    "    <key>ThumbPath</key>"         \
84    "    <string>" path "</string>"    \
85    "  </dict>"
86
87// Missing "<" delimiter at ImagePath.
88#define MALFORMED_PHOTO2(id, guid, path, caption) \
89    "  <key>" #id "</key>"             \
90    "  <dict>"                         \
91    "    <key>MediaType</key>"         \
92    "    <string>Image</string>"       \
93    "    <key>Caption<key>"            \
94    "    <string>" caption "</string>" \
95    "    <key>GUID</key>"              \
96    "    <string>" #guid "</string>"   \
97    "    <key>ImagePath/key>"          \
98    "    <string>" path "</string>"    \
99    "    <key>ThumbPath</key>"         \
100    "    <string>" path "</string>"    \
101    "  </dict>"
102
103namespace iphoto {
104
105namespace {
106
107void ComparePhoto(const parser::Photo& a, const parser::Photo& b) {
108  EXPECT_EQ(a.id, b.id);
109  EXPECT_EQ(a.location.value(), b.location.value());
110  EXPECT_EQ(a.original_location.value(), b.original_location.value());
111}
112
113void CompareAlbum(const parser::Album& a, const parser::Album& b) {
114  EXPECT_EQ(a.size(), b.size());
115
116  parser::Album::const_iterator a_it;
117  parser::Album::const_iterator b_it;
118  for (a_it = a.begin(), b_it = b.begin();
119       a_it != a.end() && b_it != b.end();
120       ++a_it, ++b_it) {
121    EXPECT_EQ(*a_it, *b_it);
122  }
123}
124
125void CompareAlbums(const parser::Albums& a, const parser::Albums& b) {
126  EXPECT_EQ(a.size(), b.size());
127
128  parser::Albums::const_iterator a_it;
129  parser::Albums::const_iterator b_it;
130  for (a_it = a.begin(), b_it = b.begin();
131       a_it != a.end() && b_it != b.end();
132       ++a_it, ++b_it) {
133    EXPECT_EQ(a_it->first, b_it->first);
134    CompareAlbum(a_it->second, b_it->second);
135  }
136}
137
138void CompareLibrary(const parser::Library& a, const parser::Library& b) {
139  CompareAlbums(a.albums, b.albums);
140
141  std::set<parser::Photo>::const_iterator a_it;
142  std::set<parser::Photo>::const_iterator b_it;
143  for (a_it = a.all_photos.begin(), b_it = b.all_photos.begin();
144       a_it != a.all_photos.end() && b_it != b.all_photos.end();
145       ++a_it, ++b_it) {
146    ComparePhoto(*a_it, *b_it);
147  }
148}
149
150class IPhotoLibraryParserTest : public testing::Test {
151 public:
152  IPhotoLibraryParserTest() {}
153
154  void TestParser(bool expected_result, const std::string& xml) {
155    IPhotoLibraryParser parser;
156
157    EXPECT_EQ(expected_result, parser.Parse(xml));
158    if (!expected_result)
159      return;
160
161    CompareLibrary(expected_library_, parser.library());
162  }
163
164  void AddExpectedPhoto(uint32 id,
165                        const std::string& location,
166                        const std::string& album) {
167    parser::Photo photo(id, base::FilePath::FromUTF8Unsafe(location),
168                        base::FilePath::FromUTF8Unsafe("/original" + location));
169    if (!album.empty())
170      expected_library_.albums[album].insert(id);
171    expected_library_.all_photos.insert(photo);
172  }
173
174 private:
175  parser::Library expected_library_;
176
177  DISALLOW_COPY_AND_ASSIGN(IPhotoLibraryParserTest);
178};
179
180TEST_F(IPhotoLibraryParserTest, EmptyLibrary) {
181  TestParser(false, "");
182}
183
184TEST_F(IPhotoLibraryParserTest, MinimalXML) {
185  AddExpectedPhoto(1, "/dir/Photo With Space.jpg", "");
186  TestParser(
187      true,
188      SIMPLE_HEADER()
189      IMAGE_LIST_HEADER()
190      SIMPLE_PHOTO(1, 1, "/dir/Photo With Space.jpg", "Photo 1")
191      IMAGE_LIST_FOOTER()
192      SIMPLE_FOOTER());
193}
194
195TEST_F(IPhotoLibraryParserTest, MultiplePhotos) {
196  AddExpectedPhoto(1, "/dir/SongA1.jpg", "");
197  AddExpectedPhoto(2, "/dir/SongA2.jpg", "");
198  AddExpectedPhoto(3, "/dir/SongA3.jpg", "");
199  AddExpectedPhoto(4, "/dir/SongB1.jpg", "");
200  AddExpectedPhoto(5, "/dir/SongB2.jpg", "");
201  AddExpectedPhoto(6, "/dir2/SongB1.jpg", "");
202  AddExpectedPhoto(7, "/dir2/SongB2.jpg", "");
203  TestParser(
204      true,
205      SIMPLE_HEADER()
206      IMAGE_LIST_HEADER()
207      SIMPLE_PHOTO(1, 1, "/dir/SongA1.jpg", "Photo 1")
208      SIMPLE_PHOTO(2, 2, "/dir/SongA2.jpg", "Photo 2")
209      SIMPLE_PHOTO(3, 3, "/dir/SongA3.jpg", "Photo 3")
210      SIMPLE_PHOTO(4, 4, "/dir/SongB1.jpg", "Photo 4")
211      SIMPLE_PHOTO(5, 5, "/dir/SongB2.jpg", "Photo 5")
212      SIMPLE_PHOTO(6, 6, "/dir2/SongB1.jpg", "Photo 6")
213      SIMPLE_PHOTO(7, 7, "/dir2/SongB2.jpg", "Photo 7")
214      IMAGE_LIST_FOOTER()
215      SIMPLE_FOOTER());
216}
217
218TEST_F(IPhotoLibraryParserTest, Albums) {
219  AddExpectedPhoto(1, "/dir/PhotoA1.jpg", "Album 1");
220  AddExpectedPhoto(2, "/dir/PhotoA2.jpg", "Album 1");
221  AddExpectedPhoto(3, "/dir/PhotoA3.jpg", "Album 2");
222  AddExpectedPhoto(4, "/dir/PhotoB1.jpg", "Album 2");
223  AddExpectedPhoto(5, "/dir/PhotoB2.jpg", "Album 3");
224  AddExpectedPhoto(6, "/dir2/PhotoB1.jpg", "Album 3");
225  AddExpectedPhoto(7, "/dir2/PhotoB2.jpg", "");
226  TestParser(
227      true,
228      SIMPLE_HEADER()
229      ALBUMS_HEADER()
230      SIMPLE_ALBUM(10, "Album 1", 1, 2)
231      SIMPLE_ALBUM(11, "Album 2", 3, 4)
232      SIMPLE_ALBUM(11, "Album/3", 5, 6)
233      ALBUMS_FOOTER()
234      IMAGE_LIST_HEADER()
235      SIMPLE_PHOTO(1, 1, "/dir/PhotoA1.jpg", "Photo 1")
236      SIMPLE_PHOTO(2, 2, "/dir/PhotoA2.jpg", "Photo 2")
237      SIMPLE_PHOTO(3, 3, "/dir/PhotoA3.jpg", "Photo 3")
238      SIMPLE_PHOTO(4, 4, "/dir/PhotoB1.jpg", "Photo 4")
239      SIMPLE_PHOTO(5, 5, "/dir/PhotoB2.jpg", "Photo 5")
240      SIMPLE_PHOTO(6, 6, "/dir2/PhotoB1.jpg", "Photo 6")
241      SIMPLE_PHOTO(7, 7, "/dir2/PhotoB2.jpg", "Photo 7")
242      IMAGE_LIST_FOOTER()
243      SIMPLE_FOOTER());
244}
245
246TEST_F(IPhotoLibraryParserTest, MalformedStructure) {
247  TestParser(
248      false,
249      SIMPLE_HEADER()
250      ALBUMS_HEADER()
251      ALBUMS_HEADER()
252      ALBUMS_FOOTER()
253      SIMPLE_FOOTER());
254
255  TestParser(
256      false,
257      SIMPLE_HEADER()
258      ALBUMS_HEADER()
259      ALBUMS_FOOTER()
260      IMAGE_LIST_HEADER()
261      IMAGE_LIST_HEADER()
262      SIMPLE_PHOTO(1, 1, "/bad.jpg", "p1")
263      IMAGE_LIST_FOOTER()
264      IMAGE_LIST_FOOTER()
265      SIMPLE_FOOTER());
266
267  TestParser(
268      false,
269      SIMPLE_HEADER()
270      ALBUMS_HEADER()
271      ALBUMS_FOOTER()
272      IMAGE_LIST_HEADER()
273      ALBUMS_HEADER()
274      SIMPLE_PHOTO(1, 1, "/bad.jpg", "p1")
275      IMAGE_LIST_FOOTER()
276      SIMPLE_FOOTER());
277}
278
279TEST_F(IPhotoLibraryParserTest, MalformedSyntax) {
280  TestParser(
281      false,
282      SIMPLE_HEADER()
283      ALBUMS_HEADER()
284      ALBUMS_FOOTER()
285      IMAGE_LIST_HEADER()
286      MALFORMED_PHOTO1(1, 1, "/bad.jpg", "p1")
287      IMAGE_LIST_FOOTER()
288      SIMPLE_FOOTER());
289
290  TestParser(
291      false,
292      SIMPLE_HEADER()
293      ALBUMS_HEADER()
294      ALBUMS_FOOTER()
295      IMAGE_LIST_HEADER()
296      MALFORMED_PHOTO2(1, 1, "/bad.jpg", "p1")
297      IMAGE_LIST_FOOTER()
298      SIMPLE_FOOTER());
299}
300
301TEST_F(IPhotoLibraryParserTest, DuplicateAlbumNames) {
302  AddExpectedPhoto(1, "/dir/PhotoA1.jpg", "Album 1");
303  AddExpectedPhoto(2, "/dir/PhotoA2.jpg", "Album 1");
304  AddExpectedPhoto(3, "/dir/PhotoA3.jpg", "Album 1(11)");
305  AddExpectedPhoto(4, "/dir/PhotoB1.jpg", "Album 1(11)");
306  TestParser(
307      true,
308      SIMPLE_HEADER()
309      ALBUMS_HEADER()
310      SIMPLE_ALBUM(10, "Album 1", 1, 2)
311      SIMPLE_ALBUM(11, "Album 1", 3, 4)
312      ALBUMS_FOOTER()
313      IMAGE_LIST_HEADER()
314      SIMPLE_PHOTO(1, 1, "/dir/PhotoA1.jpg", "Photo 1")
315      SIMPLE_PHOTO(2, 2, "/dir/PhotoA2.jpg", "Photo 2")
316      SIMPLE_PHOTO(3, 3, "/dir/PhotoA3.jpg", "Photo 3")
317      SIMPLE_PHOTO(4, 4, "/dir/PhotoB1.jpg", "Photo 4")
318      IMAGE_LIST_FOOTER()
319      SIMPLE_FOOTER());
320}
321
322}  // namespace
323
324}  // namespace iphoto
325