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_downloader.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/thread_task_runner_handle.h"
12#include "google_apis/gaia/fake_oauth2_token_service.h"
13#include "google_apis/gaia/gaia_constants.h"
14#include "net/url_request/test_url_fetcher_factory.h"
15#include "net/url_request/url_request_test_util.h"
16#include "sync/api/attachments/attachment.h"
17#include "testing/gtest/include/gtest/gtest.h"
18
19namespace syncer {
20
21namespace {
22
23const char kAccountId[] = "attachments@gmail.com";
24const char kAccessToken[] = "access.token";
25const char kAttachmentServerUrl[] = "http://attachments.com/";
26const char kAttachmentContent[] = "attachment.content";
27
28// MockOAuth2TokenService remembers last request for access token and verifies
29// that only one request is active at a time.
30// Call RespondToAccessTokenRequest to respond to it.
31class MockOAuth2TokenService : public FakeOAuth2TokenService {
32 public:
33  MockOAuth2TokenService() : num_invalidate_token_(0) {}
34
35  virtual ~MockOAuth2TokenService() {}
36
37  void RespondToAccessTokenRequest(GoogleServiceAuthError error);
38
39  int num_invalidate_token() const { return num_invalidate_token_; }
40
41 protected:
42  virtual void FetchOAuth2Token(RequestImpl* request,
43                                const std::string& account_id,
44                                net::URLRequestContextGetter* getter,
45                                const std::string& client_id,
46                                const std::string& client_secret,
47                                const ScopeSet& scopes) OVERRIDE;
48
49  virtual void InvalidateOAuth2Token(const std::string& account_id,
50                                     const std::string& client_id,
51                                     const ScopeSet& scopes,
52                                     const std::string& access_token) OVERRIDE;
53
54 private:
55  base::WeakPtr<RequestImpl> last_request_;
56  int num_invalidate_token_;
57};
58
59void MockOAuth2TokenService::RespondToAccessTokenRequest(
60    GoogleServiceAuthError error) {
61  EXPECT_TRUE(last_request_ != NULL);
62  std::string access_token;
63  base::Time expiration_time;
64  if (error == GoogleServiceAuthError::AuthErrorNone()) {
65    access_token = kAccessToken;
66    expiration_time = base::Time::Max();
67  }
68  base::MessageLoop::current()->PostTask(
69      FROM_HERE,
70      base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
71                 last_request_,
72                 error,
73                 access_token,
74                 expiration_time));
75}
76
77void MockOAuth2TokenService::FetchOAuth2Token(
78    RequestImpl* request,
79    const std::string& account_id,
80    net::URLRequestContextGetter* getter,
81    const std::string& client_id,
82    const std::string& client_secret,
83    const ScopeSet& scopes) {
84  // Only one request at a time is allowed.
85  EXPECT_TRUE(last_request_ == NULL);
86  last_request_ = request->AsWeakPtr();
87}
88
89void MockOAuth2TokenService::InvalidateOAuth2Token(
90    const std::string& account_id,
91    const std::string& client_id,
92    const ScopeSet& scopes,
93    const std::string& access_token) {
94  ++num_invalidate_token_;
95}
96
97class TokenServiceProvider
98    : public OAuth2TokenServiceRequest::TokenServiceProvider,
99      base::NonThreadSafe {
100 public:
101  TokenServiceProvider(OAuth2TokenService* token_service);
102
103  // OAuth2TokenService::TokenServiceProvider implementation.
104  virtual scoped_refptr<base::SingleThreadTaskRunner>
105      GetTokenServiceTaskRunner() OVERRIDE;
106  virtual OAuth2TokenService* GetTokenService() OVERRIDE;
107
108 private:
109  virtual ~TokenServiceProvider();
110
111  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
112  OAuth2TokenService* token_service_;
113};
114
115TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
116    : task_runner_(base::ThreadTaskRunnerHandle::Get()),
117      token_service_(token_service) {
118  DCHECK(token_service_);
119}
120
121TokenServiceProvider::~TokenServiceProvider() {
122}
123
124scoped_refptr<base::SingleThreadTaskRunner>
125TokenServiceProvider::GetTokenServiceTaskRunner() {
126  return task_runner_;
127}
128
129OAuth2TokenService* TokenServiceProvider::GetTokenService() {
130  DCHECK(task_runner_->BelongsToCurrentThread());
131  return token_service_;
132}
133
134}  // namespace
135
136class AttachmentDownloaderImplTest : public testing::Test {
137 protected:
138  typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult>
139      ResultsMap;
140
141  AttachmentDownloaderImplTest() : num_completed_downloads_(0) {}
142
143  virtual void SetUp() OVERRIDE;
144  virtual void TearDown() OVERRIDE;
145
146  AttachmentDownloader* downloader() { return attachment_downloader_.get(); }
147
148  MockOAuth2TokenService* token_service() { return token_service_.get(); }
149
150  int num_completed_downloads() { return num_completed_downloads_; }
151
152  AttachmentDownloader::DownloadCallback download_callback(
153      const AttachmentId& id) {
154    return base::Bind(&AttachmentDownloaderImplTest::DownloadDone,
155                      base::Unretained(this),
156                      id);
157  }
158
159  void CompleteDownload(int response_code);
160
161  void DownloadDone(const AttachmentId& attachment_id,
162                    const AttachmentDownloader::DownloadResult& result,
163                    scoped_ptr<Attachment> attachment);
164
165  void VerifyDownloadResult(const AttachmentId& attachment_id,
166                            const AttachmentDownloader::DownloadResult& result);
167
168  void RunMessageLoop();
169
170 private:
171  base::MessageLoopForIO message_loop_;
172  scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
173  net::TestURLFetcherFactory url_fetcher_factory_;
174  scoped_ptr<MockOAuth2TokenService> token_service_;
175  scoped_ptr<AttachmentDownloader> attachment_downloader_;
176  ResultsMap download_results_;
177  int num_completed_downloads_;
178};
179
180void AttachmentDownloaderImplTest::SetUp() {
181  url_request_context_getter_ =
182      new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy());
183  url_fetcher_factory_.set_remove_fetcher_on_delete(true);
184  token_service_.reset(new MockOAuth2TokenService());
185  token_service_->AddAccount(kAccountId);
186  scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>
187      token_service_provider(new TokenServiceProvider(token_service_.get()));
188
189  OAuth2TokenService::ScopeSet scopes;
190  scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
191  attachment_downloader_ =
192      AttachmentDownloader::Create(GURL(kAttachmentServerUrl),
193                                   url_request_context_getter_,
194                                   kAccountId,
195                                   scopes,
196                                   token_service_provider);
197}
198
199void AttachmentDownloaderImplTest::TearDown() {
200  RunMessageLoop();
201}
202
203void AttachmentDownloaderImplTest::CompleteDownload(int response_code) {
204  // TestURLFetcherFactory remembers last active URLFetcher.
205  net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
206  // There should be outstanding url fetch request.
207  EXPECT_TRUE(fetcher != NULL);
208  fetcher->set_status(net::URLRequestStatus());
209  fetcher->set_response_code(response_code);
210  if (response_code == net::HTTP_OK) {
211    fetcher->SetResponseString(kAttachmentContent);
212  }
213  // Call URLFetcherDelegate.
214  net::URLFetcherDelegate* delegate = fetcher->delegate();
215  delegate->OnURLFetchComplete(fetcher);
216  RunMessageLoop();
217  // Once result is processed URLFetcher should be deleted.
218  fetcher = url_fetcher_factory_.GetFetcherByID(0);
219  EXPECT_TRUE(fetcher == NULL);
220}
221
222void AttachmentDownloaderImplTest::DownloadDone(
223    const AttachmentId& attachment_id,
224    const AttachmentDownloader::DownloadResult& result,
225    scoped_ptr<Attachment> attachment) {
226  download_results_.insert(std::make_pair(attachment_id, result));
227  if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) {
228    // Successful download should be accompanied by valid attachment with
229    // matching id and valid data.
230    EXPECT_TRUE(attachment != NULL);
231    EXPECT_EQ(attachment_id, attachment->GetId());
232
233    scoped_refptr<base::RefCountedMemory> data = attachment->GetData();
234    std::string data_as_string(data->front_as<char>(), data->size());
235    EXPECT_EQ(data_as_string, kAttachmentContent);
236  } else {
237    EXPECT_TRUE(attachment == NULL);
238  }
239  ++num_completed_downloads_;
240}
241
242void AttachmentDownloaderImplTest::VerifyDownloadResult(
243    const AttachmentId& attachment_id,
244    const AttachmentDownloader::DownloadResult& result) {
245  ResultsMap::const_iterator iter = download_results_.find(attachment_id);
246  EXPECT_TRUE(iter != download_results_.end());
247  EXPECT_EQ(iter->second, result);
248}
249
250void AttachmentDownloaderImplTest::RunMessageLoop() {
251  base::RunLoop run_loop;
252  run_loop.RunUntilIdle();
253}
254
255TEST_F(AttachmentDownloaderImplTest, HappyCase) {
256  AttachmentId id1 = AttachmentId::Create();
257  // DownloadAttachment should trigger RequestAccessToken.
258  downloader()->DownloadAttachment(id1, download_callback(id1));
259  RunMessageLoop();
260  // Return valid access token.
261  token_service()->RespondToAccessTokenRequest(
262      GoogleServiceAuthError::AuthErrorNone());
263  RunMessageLoop();
264  // Check that there is outstanding URLFetcher request and complete it.
265  CompleteDownload(net::HTTP_OK);
266  // Verify that callback was called for the right id with the right result.
267  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
268}
269
270TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) {
271  AttachmentId id1 = AttachmentId::Create();
272  // Call DownloadAttachment two times for the same id.
273  downloader()->DownloadAttachment(id1, download_callback(id1));
274  downloader()->DownloadAttachment(id1, download_callback(id1));
275  RunMessageLoop();
276  // Return valid access token.
277  token_service()->RespondToAccessTokenRequest(
278      GoogleServiceAuthError::AuthErrorNone());
279  RunMessageLoop();
280  // Start one more download after access token is received.
281  downloader()->DownloadAttachment(id1, download_callback(id1));
282  // Complete URLFetcher request.
283  CompleteDownload(net::HTTP_OK);
284  // Verify that all download requests completed.
285  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
286  EXPECT_EQ(3, num_completed_downloads());
287
288  // Let's download the same attachment again.
289  downloader()->DownloadAttachment(id1, download_callback(id1));
290  RunMessageLoop();
291  // Verify that it didn't finish prematurely.
292  EXPECT_EQ(3, num_completed_downloads());
293  // Return valid access token.
294  token_service()->RespondToAccessTokenRequest(
295      GoogleServiceAuthError::AuthErrorNone());
296  RunMessageLoop();
297  // Complete URLFetcher request.
298  CompleteDownload(net::HTTP_OK);
299  // Verify that all download requests completed.
300  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
301  EXPECT_EQ(4, num_completed_downloads());
302}
303
304TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) {
305  AttachmentId id1 = AttachmentId::Create();
306  AttachmentId id2 = AttachmentId::Create();
307  // Trigger first RequestAccessToken.
308  downloader()->DownloadAttachment(id1, download_callback(id1));
309  RunMessageLoop();
310  // Return valid access token.
311  token_service()->RespondToAccessTokenRequest(
312      GoogleServiceAuthError::AuthErrorNone());
313  RunMessageLoop();
314  // Trigger second RequestAccessToken.
315  downloader()->DownloadAttachment(id2, download_callback(id2));
316  RunMessageLoop();
317  // Fail RequestAccessToken.
318  token_service()->RespondToAccessTokenRequest(
319      GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
320  RunMessageLoop();
321  // Only id2 should fail.
322  VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
323  // Complete request for id1.
324  CompleteDownload(net::HTTP_OK);
325  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
326}
327
328TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) {
329  AttachmentId id1 = AttachmentId::Create();
330  downloader()->DownloadAttachment(id1, download_callback(id1));
331  RunMessageLoop();
332  // Return valid access token.
333  token_service()->RespondToAccessTokenRequest(
334      GoogleServiceAuthError::AuthErrorNone());
335  RunMessageLoop();
336  // Fail URLFetcher. This should trigger download failure and access token
337  // invalidation.
338  CompleteDownload(net::HTTP_UNAUTHORIZED);
339  EXPECT_EQ(1, token_service()->num_invalidate_token());
340  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
341}
342
343TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) {
344  AttachmentId id1 = AttachmentId::Create();
345  downloader()->DownloadAttachment(id1, download_callback(id1));
346  RunMessageLoop();
347  // Return valid access token.
348  token_service()->RespondToAccessTokenRequest(
349      GoogleServiceAuthError::AuthErrorNone());
350  RunMessageLoop();
351  // Fail URLFetcher. This should trigger download failure. Access token
352  // shouldn't be invalidated.
353  CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE);
354  EXPECT_EQ(0, token_service()->num_invalidate_token());
355  VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
356}
357
358}  // namespace syncer
359