1// Copyright (c) 2012 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 "chrome/browser/chromeos/contacts/gdata_contacts_service.h" 6 7#include "base/bind.h" 8#include "base/files/file_path.h" 9#include "base/message_loop/message_loop.h" 10#include "base/strings/stringprintf.h" 11#include "base/time/time.h" 12#include "chrome/browser/chromeos/contacts/contact.pb.h" 13#include "chrome/browser/chromeos/contacts/contact_test_util.h" 14#include "chrome/browser/ui/browser.h" 15#include "chrome/test/base/in_process_browser_test.h" 16#include "content/public/browser/browser_thread.h" 17#include "content/public/test/test_browser_thread.h" 18#include "content/public/test/test_utils.h" 19#include "google_apis/drive/dummy_auth_service.h" 20#include "google_apis/drive/test_util.h" 21#include "google_apis/drive/time_util.h" 22#include "net/test/embedded_test_server/embedded_test_server.h" 23#include "net/test/embedded_test_server/http_request.h" 24#include "net/test/embedded_test_server/http_response.h" 25#include "net/url_request/url_request_test_util.h" 26#include "testing/gtest/include/gtest/gtest.h" 27#include "ui/gfx/size.h" 28 29using content::BrowserThread; 30 31namespace contacts { 32namespace { 33 34// Filename of JSON feed containing contact groups. 35const char kGroupsFeedFilename[] = "/groups.json"; 36 37// Width and height of /photo.png on the test server. 38const int kPhotoSize = 48; 39 40// Initializes |contact| using the passed-in values. 41void InitContact(const std::string& contact_id, 42 const std::string& rfc_3339_update_time, 43 bool deleted, 44 const std::string& full_name, 45 const std::string& given_name, 46 const std::string& additional_name, 47 const std::string& family_name, 48 const std::string& name_prefix, 49 const std::string& name_suffix, 50 contacts::Contact* contact) { 51 DCHECK(contact); 52 contact->set_contact_id(contact_id); 53 base::Time update_time; 54 CHECK(google_apis::util::GetTimeFromString( 55 rfc_3339_update_time, &update_time)) 56 << "Unable to parse time \"" << rfc_3339_update_time << "\""; 57 contact->set_update_time(update_time.ToInternalValue()); 58 contact->set_deleted(deleted); 59 contact->set_full_name(full_name); 60 contact->set_given_name(given_name); 61 contact->set_additional_name(additional_name); 62 contact->set_family_name(family_name); 63 contact->set_name_prefix(name_prefix); 64 contact->set_name_suffix(name_suffix); 65} 66 67class GDataContactsServiceTest : public testing::Test { 68 public: 69 GDataContactsServiceTest() 70 : ui_thread_(content::BrowserThread::UI, &message_loop_), 71 io_thread_(content::BrowserThread::IO), 72 download_was_successful_(false) { 73 } 74 75 virtual void SetUp() OVERRIDE { 76 io_thread_.StartIOThread(); 77 request_context_getter_ = new net::TestURLRequestContextGetter( 78 content::BrowserThread::GetMessageLoopProxyForThread( 79 content::BrowserThread::IO)); 80 81 test_server_.reset(new net::test_server::EmbeddedTestServer); 82 ASSERT_TRUE(test_server_->InitializeAndWaitUntilReady()); 83 test_server_->RegisterRequestHandler( 84 base::Bind(&GDataContactsServiceTest::HandleDownloadRequest, 85 base::Unretained(this))); 86 service_.reset(new GDataContactsService(request_context_getter_.get(), 87 new google_apis::DummyAuthService)); 88 service_->set_rewrite_photo_url_callback_for_testing( 89 base::Bind(&GDataContactsServiceTest::RewritePhotoUrl, 90 base::Unretained(this))); 91 service_->set_groups_feed_url_for_testing( 92 test_server_->GetURL(kGroupsFeedFilename)); 93 service_->set_photo_download_timer_interval_for_testing( 94 base::TimeDelta::FromMilliseconds(10)); 95 } 96 97 virtual void TearDown() OVERRIDE { 98 EXPECT_TRUE(test_server_->ShutdownAndWaitUntilComplete()); 99 test_server_.reset(); 100 request_context_getter_ = NULL; 101 service_.reset(); 102 } 103 104 protected: 105 // Downloads contacts from |feed_filename| (within the chromeos/gdata/contacts 106 // test data directory). |min_update_time| is appended to the URL and the 107 // resulting contacts are swapped into |contacts|. Returns false if the 108 // download failed. 109 bool Download(const std::string& feed_filename, 110 const base::Time& min_update_time, 111 scoped_ptr<ScopedVector<contacts::Contact> >* contacts) { 112 DCHECK(contacts); 113 service_->set_contacts_feed_url_for_testing( 114 test_server_->GetURL(feed_filename)); 115 service_->DownloadContacts( 116 base::Bind(&GDataContactsServiceTest::OnSuccess, 117 base::Unretained(this)), 118 base::Bind(&GDataContactsServiceTest::OnFailure, 119 base::Unretained(this)), 120 min_update_time); 121 content::RunMessageLoop(); 122 contacts->swap(downloaded_contacts_); 123 return download_was_successful_; 124 } 125 126 scoped_ptr<GDataContactsService> service_; 127 scoped_ptr<net::test_server::EmbeddedTestServer> test_server_; 128 129 private: 130 // Rewrites |original_url|, a photo URL from a contacts feed, to instead point 131 // at a file on |test_server_|. 132 std::string RewritePhotoUrl(const std::string& original_url) { 133 return test_server_->GetURL(GURL(original_url).path()).spec(); 134 } 135 136 // Handles success for Download(). 137 void OnSuccess(scoped_ptr<ScopedVector<contacts::Contact> > contacts) { 138 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 139 download_was_successful_ = true; 140 downloaded_contacts_.swap(contacts); 141 base::MessageLoop::current()->Quit(); 142 } 143 144 // Handles failure for Download(). 145 void OnFailure() { 146 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 147 download_was_successful_ = false; 148 downloaded_contacts_.reset(new ScopedVector<contacts::Contact>()); 149 base::MessageLoop::current()->Quit(); 150 } 151 152 // Handles a request for downloading a file. Reads a requested file and 153 // returns the content. 154 scoped_ptr<net::test_server::HttpResponse> HandleDownloadRequest( 155 const net::test_server::HttpRequest& request) { 156 // Requested url must not contain a query string. 157 scoped_ptr<net::test_server::BasicHttpResponse> result = 158 google_apis::test_util::CreateHttpResponseFromFile( 159 google_apis::test_util::GetTestFilePath( 160 std::string("chromeos/gdata/contacts") + request.relative_url)); 161 return result.PassAs<net::test_server::HttpResponse>(); 162 } 163 164 base::MessageLoopForUI message_loop_; 165 content::TestBrowserThread ui_thread_; 166 content::TestBrowserThread io_thread_; 167 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; 168 169 // Was the last download successful? Used to pass the result back from 170 // OnSuccess() and OnFailure() to Download(). 171 bool download_was_successful_; 172 173 // Used to pass downloaded contacts back to Download(). 174 scoped_ptr<ScopedVector<contacts::Contact> > downloaded_contacts_; 175}; 176 177} // namespace 178 179// Test that we report failure for feeds that are broken in various ways. 180TEST_F(GDataContactsServiceTest, BrokenFeeds) { 181 scoped_ptr<ScopedVector<contacts::Contact> > contacts; 182 EXPECT_FALSE(Download("/some_bogus_file", base::Time(), &contacts)); 183 EXPECT_FALSE(Download("/empty.txt", base::Time(), &contacts)); 184 EXPECT_FALSE(Download("/not_json.txt", base::Time(), &contacts)); 185 EXPECT_FALSE(Download("/not_dictionary.json", base::Time(), &contacts)); 186 EXPECT_FALSE(Download("/no_feed.json", base::Time(), &contacts)); 187 EXPECT_FALSE(Download("/no_category.json", base::Time(), &contacts)); 188 EXPECT_FALSE(Download("/wrong_category.json", base::Time(), &contacts)); 189 190 // Missing photos should be allowed, though (as this can occur in production). 191 EXPECT_TRUE(Download("/feed_photo_404.json", base::Time(), &contacts)); 192 ASSERT_EQ(static_cast<size_t>(1), contacts->size()); 193 EXPECT_FALSE((*contacts)[0]->has_raw_untrusted_photo()); 194 195 // We should report failure when we're unable to download the contact group 196 // feed. 197 service_->clear_cached_my_contacts_group_id_for_testing(); 198 service_->set_groups_feed_url_for_testing( 199 test_server_->GetURL("/404")); 200 EXPECT_FALSE(Download("/feed.json", base::Time(), &contacts)); 201 EXPECT_TRUE(service_->cached_my_contacts_group_id_for_testing().empty()); 202 203 // We should also fail when the "My Contacts" group isn't listed in the group 204 // feed. 205 service_->clear_cached_my_contacts_group_id_for_testing(); 206 service_->set_groups_feed_url_for_testing( 207 test_server_->GetURL("/groups_no_my_contacts.json")); 208 EXPECT_FALSE(Download("/feed.json", base::Time(), &contacts)); 209 EXPECT_TRUE(service_->cached_my_contacts_group_id_for_testing().empty()); 210} 211 212// Check that we're able to download an empty feed and a normal-looking feed 213// with two regular contacts and one deleted one. 214TEST_F(GDataContactsServiceTest, Download) { 215 scoped_ptr<ScopedVector<contacts::Contact> > contacts; 216 EXPECT_TRUE(Download("/no_entries.json", base::Time(), &contacts)); 217 EXPECT_TRUE(contacts->empty()); 218 219 EXPECT_TRUE(Download("/feed.json", base::Time(), &contacts)); 220 221 // Check that we got the group ID for the "My Contacts" group that's hardcoded 222 // in the groups feed. 223 EXPECT_EQ( 224 "http://www.google.com/m8/feeds/groups/test.user%40gmail.com/base/6", 225 service_->cached_my_contacts_group_id_for_testing()); 226 227 // All of these expected values are hardcoded in the feed. 228 scoped_ptr<contacts::Contact> contact1(new contacts::Contact); 229 InitContact("http://example.com/1", 230 "2012-06-04T15:53:36.023Z", 231 false, "Joe Contact", "Joe", "", "Contact", "", "", 232 contact1.get()); 233 contacts::test::SetPhoto(gfx::Size(kPhotoSize, kPhotoSize), contact1.get()); 234 contacts::test::AddEmailAddress( 235 "joe.contact@gmail.com", 236 contacts::Contact_AddressType_Relation_OTHER, "", true, contact1.get()); 237 contacts::test::AddPostalAddress( 238 "345 Spear St\nSan Francisco CA 94105", 239 contacts::Contact_AddressType_Relation_HOME, "", false, contact1.get()); 240 241 scoped_ptr<contacts::Contact> contact2(new contacts::Contact); 242 InitContact("http://example.com/2", 243 "2012-06-21T16:20:13.208Z", 244 false, "Dr. Jane Liz Doe Sr.", "Jane", "Liz", "Doe", "Dr.", "Sr.", 245 contact2.get()); 246 contacts::test::AddEmailAddress( 247 "jane.doe@gmail.com", 248 contacts::Contact_AddressType_Relation_HOME, "", true, contact2.get()); 249 contacts::test::AddEmailAddress( 250 "me@privacy.net", 251 contacts::Contact_AddressType_Relation_WORK, "", false, contact2.get()); 252 contacts::test::AddEmailAddress( 253 "foo@example.org", 254 contacts::Contact_AddressType_Relation_OTHER, "Fake", false, 255 contact2.get()); 256 contacts::test::AddPhoneNumber( 257 "123-456-7890", 258 contacts::Contact_AddressType_Relation_MOBILE, "", false, 259 contact2.get()); 260 contacts::test::AddPhoneNumber( 261 "234-567-8901", 262 contacts::Contact_AddressType_Relation_OTHER, "grandcentral", false, 263 contact2.get()); 264 contacts::test::AddPostalAddress( 265 "100 Elm St\nSan Francisco, CA 94110", 266 contacts::Contact_AddressType_Relation_HOME, "", false, contact2.get()); 267 contacts::test::AddInstantMessagingAddress( 268 "foo@example.org", 269 contacts::Contact_InstantMessagingAddress_Protocol_GOOGLE_TALK, 270 contacts::Contact_AddressType_Relation_OTHER, "", false, 271 contact2.get()); 272 contacts::test::AddInstantMessagingAddress( 273 "12345678", 274 contacts::Contact_InstantMessagingAddress_Protocol_ICQ, 275 contacts::Contact_AddressType_Relation_OTHER, "", false, 276 contact2.get()); 277 278 scoped_ptr<contacts::Contact> contact3(new contacts::Contact); 279 InitContact("http://example.com/3", 280 "2012-07-23T23:07:06.133Z", 281 true, "", "", "", "", "", "", 282 contact3.get()); 283 284 EXPECT_EQ(contacts::test::VarContactsToString( 285 3, contact1.get(), contact2.get(), contact3.get()), 286 contacts::test::ContactsToString(*contacts)); 287} 288 289// Download a feed containing more photos than we're able to download in 290// parallel to check that we still end up with all the photos. 291TEST_F(GDataContactsServiceTest, ParallelPhotoDownload) { 292 // The feed used for this test contains 8 contacts. 293 const int kNumContacts = 8; 294 service_->set_max_photo_downloads_per_second_for_testing(6); 295 scoped_ptr<ScopedVector<contacts::Contact> > contacts; 296 EXPECT_TRUE(Download("/feed_multiple_photos.json", base::Time(), &contacts)); 297 ASSERT_EQ(static_cast<size_t>(kNumContacts), contacts->size()); 298 299 ScopedVector<contacts::Contact> expected_contacts; 300 for (int i = 0; i < kNumContacts; ++i) { 301 contacts::Contact* contact = new contacts::Contact; 302 InitContact(base::StringPrintf("http://example.com/%d", i + 1), 303 "2012-06-04T15:53:36.023Z", 304 false, "", "", "", "", "", "", contact); 305 contacts::test::SetPhoto(gfx::Size(kPhotoSize, kPhotoSize), contact); 306 expected_contacts.push_back(contact); 307 } 308 EXPECT_EQ(contacts::test::ContactsToString(expected_contacts), 309 contacts::test::ContactsToString(*contacts)); 310} 311 312TEST_F(GDataContactsServiceTest, UnicodeStrings) { 313 scoped_ptr<ScopedVector<contacts::Contact> > contacts; 314 EXPECT_TRUE(Download("/feed_unicode.json", base::Time(), &contacts)); 315 316 // All of these expected values are hardcoded in the feed. 317 scoped_ptr<contacts::Contact> contact1(new contacts::Contact); 318 InitContact("http://example.com/1", "2012-06-04T15:53:36.023Z", 319 false, "\xE5\xAE\x89\xE8\x97\xA4\x20\xE5\xBF\xA0\xE9\x9B\x84", 320 "\xE5\xBF\xA0\xE9\x9B\x84", "", "\xE5\xAE\x89\xE8\x97\xA4", 321 "", "", contact1.get()); 322 scoped_ptr<contacts::Contact> contact2(new contacts::Contact); 323 InitContact("http://example.com/2", "2012-06-21T16:20:13.208Z", 324 false, "Bob Smith", "Bob", "", "Smith", "", "", 325 contact2.get()); 326 EXPECT_EQ(contacts::test::VarContactsToString( 327 2, contact1.get(), contact2.get()), 328 contacts::test::ContactsToString(*contacts)); 329} 330 331} // namespace contacts 332