attachment_service_impl_unittest.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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
17namespace syncer {
18
19class 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
94class 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
127class 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
153class 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
275TEST_F(AttachmentServiceImplTest, GetStore) {
276  EXPECT_EQ(store(), attachment_service()->GetStore());
277}
278
279TEST_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
290TEST_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
306TEST_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
349TEST_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
370TEST_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
402TEST_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
423TEST_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
448TEST_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
470TEST_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.
484TEST_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.
515TEST_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