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