1 // Copyright 2014 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 "sync/internal_api/public/attachments/attachment_service_impl.h" 6 7 #include "base/bind.h" 8 #include "base/memory/weak_ptr.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/run_loop.h" 11 #include "base/timer/mock_timer.h" 12 #include "sync/internal_api/public/attachments/fake_attachment_downloader.h" 13 #include "sync/internal_api/public/attachments/fake_attachment_uploader.h" 14 #include "testing/gmock/include/gmock/gmock-matchers.h" 15 #include "testing/gtest/include/gtest/gtest.h" 16 17 namespace syncer { 18 19 class MockAttachmentStore : public AttachmentStore, 20 public base::SupportsWeakPtr<MockAttachmentStore> { 21 public: 22 MockAttachmentStore() {} 23 24 virtual void Read(const AttachmentIdList& ids, 25 const ReadCallback& callback) OVERRIDE { 26 read_ids.push_back(ids); 27 read_callbacks.push_back(callback); 28 } 29 30 virtual void Write(const AttachmentList& attachments, 31 const WriteCallback& callback) OVERRIDE { 32 write_attachments.push_back(attachments); 33 write_callbacks.push_back(callback); 34 } 35 36 virtual void Drop(const AttachmentIdList& ids, 37 const DropCallback& callback) OVERRIDE { 38 NOTREACHED(); 39 } 40 41 // Respond to Read request. Attachments found in local_attachments should be 42 // returned, everything else should be reported unavailable. 43 void RespondToRead(const AttachmentIdSet& local_attachments) { 44 scoped_refptr<base::RefCountedString> data = new base::RefCountedString(); 45 ReadCallback callback = read_callbacks.back(); 46 AttachmentIdList ids = read_ids.back(); 47 read_callbacks.pop_back(); 48 read_ids.pop_back(); 49 50 scoped_ptr<AttachmentMap> attachments(new AttachmentMap()); 51 scoped_ptr<AttachmentIdList> unavailable_attachments( 52 new AttachmentIdList()); 53 for (AttachmentIdList::const_iterator iter = ids.begin(); iter != ids.end(); 54 ++iter) { 55 if (local_attachments.find(*iter) != local_attachments.end()) { 56 Attachment attachment = Attachment::CreateWithId(*iter, data); 57 attachments->insert(std::make_pair(*iter, attachment)); 58 } else { 59 unavailable_attachments->push_back(*iter); 60 } 61 } 62 Result result = 63 unavailable_attachments->empty() ? SUCCESS : UNSPECIFIED_ERROR; 64 65 base::MessageLoop::current()->PostTask( 66 FROM_HERE, 67 base::Bind(callback, 68 result, 69 base::Passed(&attachments), 70 base::Passed(&unavailable_attachments))); 71 } 72 73 // Respond to Write request with |result|. 74 void RespondToWrite(const Result& result) { 75 WriteCallback callback = write_callbacks.back(); 76 AttachmentList attachments = write_attachments.back(); 77 write_callbacks.pop_back(); 78 write_attachments.pop_back(); 79 base::MessageLoop::current()->PostTask(FROM_HERE, 80 base::Bind(callback, result)); 81 } 82 83 std::vector<AttachmentIdList> read_ids; 84 std::vector<ReadCallback> read_callbacks; 85 std::vector<AttachmentList> write_attachments; 86 std::vector<WriteCallback> write_callbacks; 87 88 private: 89 virtual ~MockAttachmentStore() {} 90 91 DISALLOW_COPY_AND_ASSIGN(MockAttachmentStore); 92 }; 93 94 class MockAttachmentDownloader 95 : public AttachmentDownloader, 96 public base::SupportsWeakPtr<MockAttachmentDownloader> { 97 public: 98 MockAttachmentDownloader() {} 99 100 virtual void DownloadAttachment(const AttachmentId& id, 101 const DownloadCallback& callback) OVERRIDE { 102 ASSERT_TRUE(download_requests.find(id) == download_requests.end()); 103 download_requests.insert(std::make_pair(id, callback)); 104 } 105 106 // Multiple requests to download will be active at the same time. 107 // RespondToDownload should respond to only one of them. 108 void RespondToDownload(const AttachmentId& id, const DownloadResult& result) { 109 ASSERT_TRUE(download_requests.find(id) != download_requests.end()); 110 scoped_ptr<Attachment> attachment; 111 if (result == DOWNLOAD_SUCCESS) { 112 scoped_refptr<base::RefCountedString> data = new base::RefCountedString(); 113 attachment.reset(new Attachment(Attachment::CreateWithId(id, data))); 114 } 115 base::MessageLoop::current()->PostTask( 116 FROM_HERE, 117 base::Bind(download_requests[id], result, base::Passed(&attachment))); 118 119 download_requests.erase(id); 120 } 121 122 std::map<AttachmentId, DownloadCallback> download_requests; 123 124 DISALLOW_COPY_AND_ASSIGN(MockAttachmentDownloader); 125 }; 126 127 class MockAttachmentUploader 128 : public AttachmentUploader, 129 public base::SupportsWeakPtr<MockAttachmentUploader> { 130 public: 131 MockAttachmentUploader() {} 132 133 // AttachmentUploader implementation. 134 virtual void UploadAttachment(const Attachment& attachment, 135 const UploadCallback& callback) OVERRIDE { 136 const AttachmentId id = attachment.GetId(); 137 ASSERT_TRUE(upload_requests.find(id) == upload_requests.end()); 138 upload_requests.insert(std::make_pair(id, callback)); 139 } 140 141 void RespondToUpload(const AttachmentId& id, const UploadResult& result) { 142 ASSERT_TRUE(upload_requests.find(id) != upload_requests.end()); 143 base::MessageLoop::current()->PostTask( 144 FROM_HERE, base::Bind(upload_requests[id], result, id)); 145 upload_requests.erase(id); 146 } 147 148 std::map<AttachmentId, UploadCallback> upload_requests; 149 150 DISALLOW_COPY_AND_ASSIGN(MockAttachmentUploader); 151 }; 152 153 class AttachmentServiceImplTest : public testing::Test, 154 public AttachmentService::Delegate { 155 protected: 156 AttachmentServiceImplTest() {} 157 158 virtual void SetUp() OVERRIDE { 159 network_change_notifier_.reset(net::NetworkChangeNotifier::CreateMock()); 160 InitializeAttachmentService(make_scoped_ptr(new MockAttachmentUploader()), 161 make_scoped_ptr(new MockAttachmentDownloader()), 162 this); 163 } 164 165 virtual void TearDown() OVERRIDE { 166 attachment_service_.reset(); 167 ASSERT_FALSE(attachment_store_); 168 ASSERT_FALSE(attachment_uploader_); 169 ASSERT_FALSE(attachment_downloader_); 170 } 171 172 // AttachmentService::Delegate implementation. 173 virtual void OnAttachmentUploaded( 174 const AttachmentId& attachment_id) OVERRIDE { 175 on_attachment_uploaded_list_.push_back(attachment_id); 176 } 177 178 void InitializeAttachmentService( 179 scoped_ptr<MockAttachmentUploader> uploader, 180 scoped_ptr<MockAttachmentDownloader> downloader, 181 AttachmentService::Delegate* delegate) { 182 scoped_refptr<MockAttachmentStore> attachment_store( 183 new MockAttachmentStore()); 184 attachment_store_ = attachment_store->AsWeakPtr(); 185 186 if (uploader.get()) { 187 attachment_uploader_ = uploader->AsWeakPtr(); 188 } 189 if (downloader.get()) { 190 attachment_downloader_ = downloader->AsWeakPtr(); 191 } 192 attachment_service_.reset( 193 new AttachmentServiceImpl(attachment_store, 194 uploader.PassAs<AttachmentUploader>(), 195 downloader.PassAs<AttachmentDownloader>(), 196 delegate, 197 base::TimeDelta::FromMinutes(1), 198 base::TimeDelta::FromMinutes(8))); 199 200 scoped_ptr<base::MockTimer> timer_to_pass( 201 new base::MockTimer(false, false)); 202 mock_timer_ = timer_to_pass.get(); 203 attachment_service_->SetTimerForTest(timer_to_pass.PassAs<base::Timer>()); 204 } 205 206 AttachmentService* attachment_service() { return attachment_service_.get(); } 207 208 base::MockTimer* mock_timer() { return mock_timer_; } 209 210 AttachmentService::GetOrDownloadCallback download_callback() { 211 return base::Bind(&AttachmentServiceImplTest::DownloadDone, 212 base::Unretained(this)); 213 } 214 215 void DownloadDone(const AttachmentService::GetOrDownloadResult& result, 216 scoped_ptr<AttachmentMap> attachments) { 217 download_results_.push_back(result); 218 last_download_attachments_ = attachments.Pass(); 219 } 220 221 void RunLoop() { 222 base::RunLoop run_loop; 223 run_loop.RunUntilIdle(); 224 } 225 226 void RunLoopAndFireTimer() { 227 RunLoop(); 228 if (mock_timer()->IsRunning()) { 229 mock_timer()->Fire(); 230 } 231 RunLoop(); 232 } 233 234 const std::vector<AttachmentService::GetOrDownloadResult>& 235 download_results() const { 236 return download_results_; 237 } 238 239 const AttachmentMap& last_download_attachments() const { 240 return *last_download_attachments_.get(); 241 } 242 243 net::NetworkChangeNotifier* network_change_notifier() { 244 return network_change_notifier_.get(); 245 } 246 247 MockAttachmentStore* store() { return attachment_store_.get(); } 248 249 MockAttachmentDownloader* downloader() { 250 return attachment_downloader_.get(); 251 } 252 253 MockAttachmentUploader* uploader() { 254 return attachment_uploader_.get(); 255 } 256 257 const std::vector<AttachmentId>& on_attachment_uploaded_list() const { 258 return on_attachment_uploaded_list_; 259 } 260 261 private: 262 base::MessageLoop message_loop_; 263 scoped_ptr<net::NetworkChangeNotifier> network_change_notifier_; 264 base::WeakPtr<MockAttachmentStore> attachment_store_; 265 base::WeakPtr<MockAttachmentDownloader> attachment_downloader_; 266 base::WeakPtr<MockAttachmentUploader> attachment_uploader_; 267 scoped_ptr<AttachmentServiceImpl> attachment_service_; 268 base::MockTimer* mock_timer_; // not owned 269 270 std::vector<AttachmentService::GetOrDownloadResult> download_results_; 271 scoped_ptr<AttachmentMap> last_download_attachments_; 272 std::vector<AttachmentId> on_attachment_uploaded_list_; 273 }; 274 275 TEST_F(AttachmentServiceImplTest, GetStore) { 276 EXPECT_EQ(store(), attachment_service()->GetStore()); 277 } 278 279 TEST_F(AttachmentServiceImplTest, GetOrDownload_EmptyAttachmentList) { 280 AttachmentIdList attachment_ids; 281 attachment_service()->GetOrDownloadAttachments(attachment_ids, 282 download_callback()); 283 store()->RespondToRead(AttachmentIdSet()); 284 285 RunLoop(); 286 EXPECT_EQ(1U, download_results().size()); 287 EXPECT_EQ(0U, last_download_attachments().size()); 288 } 289 290 TEST_F(AttachmentServiceImplTest, GetOrDownload_Local) { 291 AttachmentIdList attachment_ids; 292 attachment_ids.push_back(AttachmentId::Create()); 293 attachment_service()->GetOrDownloadAttachments(attachment_ids, 294 download_callback()); 295 AttachmentIdSet local_attachments; 296 local_attachments.insert(attachment_ids[0]); 297 store()->RespondToRead(local_attachments); 298 299 RunLoop(); 300 EXPECT_EQ(1U, download_results().size()); 301 EXPECT_EQ(1U, last_download_attachments().size()); 302 EXPECT_TRUE(last_download_attachments().find(attachment_ids[0]) != 303 last_download_attachments().end()); 304 } 305 306 TEST_F(AttachmentServiceImplTest, GetOrDownload_LocalRemoteUnavailable) { 307 // Create attachment list with 3 ids. 308 AttachmentIdList attachment_ids; 309 attachment_ids.push_back(AttachmentId::Create()); 310 attachment_ids.push_back(AttachmentId::Create()); 311 attachment_ids.push_back(AttachmentId::Create()); 312 // Call attachment service. 313 attachment_service()->GetOrDownloadAttachments(attachment_ids, 314 download_callback()); 315 // Ensure AttachmentStore is called. 316 EXPECT_FALSE(store()->read_ids.empty()); 317 318 // make AttachmentStore return only attachment 0. 319 AttachmentIdSet local_attachments; 320 local_attachments.insert(attachment_ids[0]); 321 store()->RespondToRead(local_attachments); 322 RunLoop(); 323 // Ensure Downloader called with right attachment ids 324 EXPECT_EQ(2U, downloader()->download_requests.size()); 325 326 // Make downloader return attachment 1. 327 downloader()->RespondToDownload(attachment_ids[1], 328 AttachmentDownloader::DOWNLOAD_SUCCESS); 329 RunLoop(); 330 // Ensure consumer callback is not called. 331 EXPECT_TRUE(download_results().empty()); 332 333 // Make downloader fail attachment 2. 334 downloader()->RespondToDownload( 335 attachment_ids[2], AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR); 336 RunLoop(); 337 // Ensure callback is called 338 EXPECT_FALSE(download_results().empty()); 339 // There should be only two attachments returned, 0 and 1. 340 EXPECT_EQ(2U, last_download_attachments().size()); 341 EXPECT_TRUE(last_download_attachments().find(attachment_ids[0]) != 342 last_download_attachments().end()); 343 EXPECT_TRUE(last_download_attachments().find(attachment_ids[1]) != 344 last_download_attachments().end()); 345 EXPECT_TRUE(last_download_attachments().find(attachment_ids[2]) == 346 last_download_attachments().end()); 347 } 348 349 TEST_F(AttachmentServiceImplTest, GetOrDownload_NoDownloader) { 350 // No downloader. 351 InitializeAttachmentService( 352 make_scoped_ptr<MockAttachmentUploader>(new MockAttachmentUploader()), 353 make_scoped_ptr<MockAttachmentDownloader>(NULL), 354 this); 355 356 AttachmentIdList attachment_ids; 357 attachment_ids.push_back(AttachmentId::Create()); 358 attachment_service()->GetOrDownloadAttachments(attachment_ids, 359 download_callback()); 360 EXPECT_FALSE(store()->read_ids.empty()); 361 362 AttachmentIdSet local_attachments; 363 store()->RespondToRead(local_attachments); 364 RunLoop(); 365 ASSERT_EQ(1U, download_results().size()); 366 EXPECT_EQ(AttachmentService::GET_UNSPECIFIED_ERROR, download_results()[0]); 367 EXPECT_TRUE(last_download_attachments().empty()); 368 } 369 370 TEST_F(AttachmentServiceImplTest, UploadAttachments_Success) { 371 AttachmentIdSet attachment_ids; 372 const unsigned num_attachments = 3; 373 for (unsigned i = 0; i < num_attachments; ++i) { 374 attachment_ids.insert(AttachmentId::Create()); 375 } 376 attachment_service()->UploadAttachments(attachment_ids); 377 378 for (unsigned i = 0; i < num_attachments; ++i) { 379 RunLoopAndFireTimer(); 380 // See that the service has issued a read for at least one of the 381 // attachments. 382 ASSERT_GE(store()->read_ids.size(), 1U); 383 store()->RespondToRead(attachment_ids); 384 RunLoop(); 385 ASSERT_GE(uploader()->upload_requests.size(), 1U); 386 uploader()->RespondToUpload(uploader()->upload_requests.begin()->first, 387 AttachmentUploader::UPLOAD_SUCCESS); 388 } 389 RunLoop(); 390 ASSERT_EQ(0U, store()->read_ids.size()); 391 ASSERT_EQ(0U, uploader()->upload_requests.size()); 392 393 // See that all the attachments were uploaded. 394 ASSERT_EQ(attachment_ids.size(), on_attachment_uploaded_list().size()); 395 AttachmentIdSet::const_iterator iter = attachment_ids.begin(); 396 const AttachmentIdSet::const_iterator end = attachment_ids.end(); 397 for (iter = attachment_ids.begin(); iter != end; ++iter) { 398 EXPECT_THAT(on_attachment_uploaded_list(), testing::Contains(*iter)); 399 } 400 } 401 402 TEST_F(AttachmentServiceImplTest, UploadAttachments_Success_NoDelegate) { 403 InitializeAttachmentService(make_scoped_ptr(new MockAttachmentUploader()), 404 make_scoped_ptr(new MockAttachmentDownloader()), 405 NULL); // No delegate. 406 407 AttachmentIdSet attachment_ids; 408 attachment_ids.insert(AttachmentId::Create()); 409 attachment_service()->UploadAttachments(attachment_ids); 410 RunLoopAndFireTimer(); 411 ASSERT_EQ(1U, store()->read_ids.size()); 412 ASSERT_EQ(0U, uploader()->upload_requests.size()); 413 store()->RespondToRead(attachment_ids); 414 RunLoop(); 415 ASSERT_EQ(0U, store()->read_ids.size()); 416 ASSERT_EQ(1U, uploader()->upload_requests.size()); 417 uploader()->RespondToUpload(*attachment_ids.begin(), 418 AttachmentUploader::UPLOAD_SUCCESS); 419 RunLoop(); 420 ASSERT_TRUE(on_attachment_uploaded_list().empty()); 421 } 422 423 TEST_F(AttachmentServiceImplTest, UploadAttachments_SomeMissingFromStore) { 424 AttachmentIdSet attachment_ids; 425 attachment_ids.insert(AttachmentId::Create()); 426 attachment_ids.insert(AttachmentId::Create()); 427 attachment_service()->UploadAttachments(attachment_ids); 428 RunLoopAndFireTimer(); 429 ASSERT_GE(store()->read_ids.size(), 1U); 430 431 ASSERT_EQ(0U, uploader()->upload_requests.size()); 432 store()->RespondToRead(attachment_ids); 433 RunLoop(); 434 ASSERT_EQ(1U, uploader()->upload_requests.size()); 435 436 uploader()->RespondToUpload(uploader()->upload_requests.begin()->first, 437 AttachmentUploader::UPLOAD_SUCCESS); 438 RunLoopAndFireTimer(); 439 ASSERT_EQ(1U, on_attachment_uploaded_list().size()); 440 ASSERT_GE(store()->read_ids.size(), 1U); 441 // Not found! 442 store()->RespondToRead(AttachmentIdSet()); 443 RunLoop(); 444 // No upload requests since the read failed. 445 ASSERT_EQ(0U, uploader()->upload_requests.size()); 446 } 447 448 TEST_F(AttachmentServiceImplTest, UploadAttachments_AllMissingFromStore) { 449 AttachmentIdSet attachment_ids; 450 const unsigned num_attachments = 2; 451 for (unsigned i = 0; i < num_attachments; ++i) { 452 attachment_ids.insert(AttachmentId::Create()); 453 } 454 attachment_service()->UploadAttachments(attachment_ids); 455 456 for (unsigned i = 0; i < num_attachments; ++i) { 457 RunLoopAndFireTimer(); 458 ASSERT_GE(store()->read_ids.size(), 1U); 459 // None found! 460 store()->RespondToRead(AttachmentIdSet()); 461 } 462 RunLoop(); 463 464 // Nothing uploaded. 465 EXPECT_EQ(0U, uploader()->upload_requests.size()); 466 // See that the delegate was never called. 467 ASSERT_EQ(0U, on_attachment_uploaded_list().size()); 468 } 469 470 TEST_F(AttachmentServiceImplTest, UploadAttachments_NoUploader) { 471 InitializeAttachmentService(make_scoped_ptr<MockAttachmentUploader>(NULL), 472 make_scoped_ptr(new MockAttachmentDownloader()), 473 this); 474 475 AttachmentIdSet attachment_ids; 476 attachment_ids.insert(AttachmentId::Create()); 477 attachment_service()->UploadAttachments(attachment_ids); 478 RunLoop(); 479 EXPECT_EQ(0U, store()->read_ids.size()); 480 ASSERT_EQ(0U, on_attachment_uploaded_list().size()); 481 } 482 483 // Upload three attachments. For one of them, server responds with error. 484 TEST_F(AttachmentServiceImplTest, UploadAttachments_OneUploadFails) { 485 AttachmentIdSet attachment_ids; 486 const unsigned num_attachments = 3; 487 for (unsigned i = 0; i < num_attachments; ++i) { 488 attachment_ids.insert(AttachmentId::Create()); 489 } 490 attachment_service()->UploadAttachments(attachment_ids); 491 492 for (unsigned i = 0; i < 3; ++i) { 493 RunLoopAndFireTimer(); 494 ASSERT_GE(store()->read_ids.size(), 1U); 495 store()->RespondToRead(attachment_ids); 496 RunLoop(); 497 ASSERT_EQ(1U, uploader()->upload_requests.size()); 498 AttachmentUploader::UploadResult result = 499 AttachmentUploader::UPLOAD_SUCCESS; 500 // Fail the 2nd one. 501 if (i == 2U) { 502 result = AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR; 503 } else { 504 result = AttachmentUploader::UPLOAD_SUCCESS; 505 } 506 uploader()->RespondToUpload(uploader()->upload_requests.begin()->first, 507 result); 508 RunLoop(); 509 } 510 ASSERT_EQ(2U, on_attachment_uploaded_list().size()); 511 } 512 513 // Attempt an upload, respond with transient error to trigger backoff, issue 514 // network disconnect/connect events and see that backoff is cleared. 515 TEST_F(AttachmentServiceImplTest, 516 UploadAttachments_ResetBackoffAfterNetworkChange) { 517 AttachmentIdSet attachment_ids; 518 attachment_ids.insert(AttachmentId::Create()); 519 attachment_service()->UploadAttachments(attachment_ids); 520 521 RunLoopAndFireTimer(); 522 ASSERT_EQ(1U, store()->read_ids.size()); 523 store()->RespondToRead(attachment_ids); 524 RunLoop(); 525 ASSERT_EQ(1U, uploader()->upload_requests.size()); 526 527 uploader()->RespondToUpload(uploader()->upload_requests.begin()->first, 528 AttachmentUploader::UPLOAD_TRANSIENT_ERROR); 529 RunLoop(); 530 531 // See that we are in backoff. 532 ASSERT_TRUE(mock_timer()->IsRunning()); 533 ASSERT_GT(mock_timer()->GetCurrentDelay(), base::TimeDelta()); 534 535 // Issue a network disconnect event. 536 network_change_notifier()->NotifyObserversOfNetworkChangeForTests( 537 net::NetworkChangeNotifier::CONNECTION_NONE); 538 RunLoop(); 539 540 // Still in backoff. 541 ASSERT_TRUE(mock_timer()->IsRunning()); 542 ASSERT_GT(mock_timer()->GetCurrentDelay(), base::TimeDelta()); 543 544 // Issue a network connect event. 545 net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( 546 net::NetworkChangeNotifier::CONNECTION_WIFI); 547 RunLoop(); 548 549 // No longer in backoff. 550 ASSERT_TRUE(mock_timer()->IsRunning()); 551 ASSERT_EQ(base::TimeDelta(), mock_timer()->GetCurrentDelay()); 552 } 553 554 } // namespace syncer 555