Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / sync / internal_api / attachments / attachment_downloader_impl_unittest.cc
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_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/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/http/http_response_headers.h"
15 #include "net/url_request/test_url_fetcher_factory.h"
16 #include "net/url_request/url_request_test_util.h"
17 #include "sync/api/attachments/attachment.h"
18 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20
21 namespace syncer {
22
23 namespace {
24
25 const char kAccountId[] = "attachments@gmail.com";
26 const char kAccessToken[] = "access.token";
27 const char kAttachmentServerUrl[] = "http://attachments.com/";
28 const char kAttachmentContent[] = "attachment.content";
29
30 // MockOAuth2TokenService remembers last request for access token and verifies
31 // that only one request is active at a time.
32 // Call RespondToAccessTokenRequest to respond to it.
33 class MockOAuth2TokenService : public FakeOAuth2TokenService {
34  public:
35   MockOAuth2TokenService() : num_invalidate_token_(0) {}
36
37   ~MockOAuth2TokenService() override {}
38
39   void RespondToAccessTokenRequest(GoogleServiceAuthError error);
40
41   int num_invalidate_token() const { return num_invalidate_token_; }
42
43  protected:
44   void FetchOAuth2Token(RequestImpl* request,
45                         const std::string& account_id,
46                         net::URLRequestContextGetter* getter,
47                         const std::string& client_id,
48                         const std::string& client_secret,
49                         const ScopeSet& scopes) override;
50
51   void InvalidateOAuth2Token(const std::string& account_id,
52                              const std::string& client_id,
53                              const ScopeSet& scopes,
54                              const std::string& access_token) override;
55
56  private:
57   base::WeakPtr<RequestImpl> last_request_;
58   int num_invalidate_token_;
59 };
60
61 void MockOAuth2TokenService::RespondToAccessTokenRequest(
62     GoogleServiceAuthError error) {
63   EXPECT_TRUE(last_request_ != NULL);
64   std::string access_token;
65   base::Time expiration_time;
66   if (error == GoogleServiceAuthError::AuthErrorNone()) {
67     access_token = kAccessToken;
68     expiration_time = base::Time::Max();
69   }
70   base::MessageLoop::current()->PostTask(
71       FROM_HERE,
72       base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
73                  last_request_,
74                  error,
75                  access_token,
76                  expiration_time));
77 }
78
79 void MockOAuth2TokenService::FetchOAuth2Token(
80     RequestImpl* request,
81     const std::string& account_id,
82     net::URLRequestContextGetter* getter,
83     const std::string& client_id,
84     const std::string& client_secret,
85     const ScopeSet& scopes) {
86   // Only one request at a time is allowed.
87   EXPECT_TRUE(last_request_ == NULL);
88   last_request_ = request->AsWeakPtr();
89 }
90
91 void MockOAuth2TokenService::InvalidateOAuth2Token(
92     const std::string& account_id,
93     const std::string& client_id,
94     const ScopeSet& scopes,
95     const std::string& access_token) {
96   ++num_invalidate_token_;
97 }
98
99 class TokenServiceProvider
100     : public OAuth2TokenServiceRequest::TokenServiceProvider,
101       base::NonThreadSafe {
102  public:
103   TokenServiceProvider(OAuth2TokenService* token_service);
104
105   // OAuth2TokenService::TokenServiceProvider implementation.
106   scoped_refptr<base::SingleThreadTaskRunner> GetTokenServiceTaskRunner()
107       override;
108   OAuth2TokenService* GetTokenService() override;
109
110  private:
111   ~TokenServiceProvider() override;
112
113   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
114   OAuth2TokenService* token_service_;
115 };
116
117 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
118     : task_runner_(base::ThreadTaskRunnerHandle::Get()),
119       token_service_(token_service) {
120   DCHECK(token_service_);
121 }
122
123 TokenServiceProvider::~TokenServiceProvider() {
124 }
125
126 scoped_refptr<base::SingleThreadTaskRunner>
127 TokenServiceProvider::GetTokenServiceTaskRunner() {
128   return task_runner_;
129 }
130
131 OAuth2TokenService* TokenServiceProvider::GetTokenService() {
132   DCHECK(task_runner_->BelongsToCurrentThread());
133   return token_service_;
134 }
135
136 }  // namespace
137
138 class AttachmentDownloaderImplTest : public testing::Test {
139  protected:
140   typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult>
141       ResultsMap;
142
143   enum HashHeaderType {
144     HASH_HEADER_NONE,
145     HASH_HEADER_VALID,
146     HASH_HEADER_INVALID
147   };
148
149   AttachmentDownloaderImplTest() : num_completed_downloads_(0) {}
150
151   virtual void SetUp() override;
152   virtual void TearDown() override;
153
154   AttachmentDownloader* downloader() { return attachment_downloader_.get(); }
155
156   MockOAuth2TokenService* token_service() { return token_service_.get(); }
157
158   int num_completed_downloads() { return num_completed_downloads_; }
159
160   AttachmentDownloader::DownloadCallback download_callback(
161       const AttachmentId& id) {
162     return base::Bind(&AttachmentDownloaderImplTest::DownloadDone,
163                       base::Unretained(this),
164                       id);
165   }
166
167   // Respond with |response_code| and hash header of type |hash_header_type|.
168   void CompleteDownload(int response_code, HashHeaderType hash_header_type);
169
170   void DownloadDone(const AttachmentId& attachment_id,
171                     const AttachmentDownloader::DownloadResult& result,
172                     scoped_ptr<Attachment> attachment);
173
174   void VerifyDownloadResult(const AttachmentId& attachment_id,
175                             const AttachmentDownloader::DownloadResult& result);
176
177   void RunMessageLoop();
178
179  private:
180   static void AddHashHeader(HashHeaderType hash_header_type,
181                             net::TestURLFetcher* fetcher);
182
183   base::MessageLoopForIO message_loop_;
184   scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
185   net::TestURLFetcherFactory url_fetcher_factory_;
186   scoped_ptr<MockOAuth2TokenService> token_service_;
187   scoped_ptr<AttachmentDownloader> attachment_downloader_;
188   ResultsMap download_results_;
189   int num_completed_downloads_;
190 };
191
192 void AttachmentDownloaderImplTest::SetUp() {
193   url_request_context_getter_ =
194       new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy());
195   url_fetcher_factory_.set_remove_fetcher_on_delete(true);
196   token_service_.reset(new MockOAuth2TokenService());
197   token_service_->AddAccount(kAccountId);
198   scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>
199       token_service_provider(new TokenServiceProvider(token_service_.get()));
200
201   OAuth2TokenService::ScopeSet scopes;
202   scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
203   attachment_downloader_ =
204       AttachmentDownloader::Create(GURL(kAttachmentServerUrl),
205                                    url_request_context_getter_,
206                                    kAccountId,
207                                    scopes,
208                                    token_service_provider);
209 }
210
211 void AttachmentDownloaderImplTest::TearDown() {
212   RunMessageLoop();
213 }
214
215 void AttachmentDownloaderImplTest::CompleteDownload(
216     int response_code,
217     HashHeaderType hash_header_type) {
218   // TestURLFetcherFactory remembers last active URLFetcher.
219   net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
220   // There should be outstanding url fetch request.
221   EXPECT_TRUE(fetcher != NULL);
222   fetcher->set_status(net::URLRequestStatus());
223   fetcher->set_response_code(response_code);
224   if (response_code == net::HTTP_OK) {
225     fetcher->SetResponseString(kAttachmentContent);
226   }
227   AddHashHeader(hash_header_type, fetcher);
228
229   // Call URLFetcherDelegate.
230   net::URLFetcherDelegate* delegate = fetcher->delegate();
231   delegate->OnURLFetchComplete(fetcher);
232   RunMessageLoop();
233   // Once result is processed URLFetcher should be deleted.
234   fetcher = url_fetcher_factory_.GetFetcherByID(0);
235   EXPECT_TRUE(fetcher == NULL);
236 }
237
238 void AttachmentDownloaderImplTest::DownloadDone(
239     const AttachmentId& attachment_id,
240     const AttachmentDownloader::DownloadResult& result,
241     scoped_ptr<Attachment> attachment) {
242   download_results_.insert(std::make_pair(attachment_id, result));
243   if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) {
244     // Successful download should be accompanied by valid attachment with
245     // matching id and valid data.
246     EXPECT_TRUE(attachment != NULL);
247     EXPECT_EQ(attachment_id, attachment->GetId());
248
249     scoped_refptr<base::RefCountedMemory> data = attachment->GetData();
250     std::string data_as_string(data->front_as<char>(), data->size());
251     EXPECT_EQ(data_as_string, kAttachmentContent);
252   } else {
253     EXPECT_TRUE(attachment == NULL);
254   }
255   ++num_completed_downloads_;
256 }
257
258 void AttachmentDownloaderImplTest::VerifyDownloadResult(
259     const AttachmentId& attachment_id,
260     const AttachmentDownloader::DownloadResult& result) {
261   ResultsMap::const_iterator iter = download_results_.find(attachment_id);
262   EXPECT_TRUE(iter != download_results_.end());
263   EXPECT_EQ(iter->second, result);
264 }
265
266 void AttachmentDownloaderImplTest::RunMessageLoop() {
267   base::RunLoop run_loop;
268   run_loop.RunUntilIdle();
269 }
270
271 void AttachmentDownloaderImplTest::AddHashHeader(
272     HashHeaderType hash_header_type,
273     net::TestURLFetcher* fetcher) {
274   std::string header = "X-Goog-Hash: crc32c=";
275   scoped_refptr<net::HttpResponseHeaders> headers(
276       new net::HttpResponseHeaders(""));
277   switch (hash_header_type) {
278     case HASH_HEADER_NONE:
279       break;
280     case HASH_HEADER_VALID:
281       header += AttachmentUploaderImpl::ComputeCrc32cHash(
282           kAttachmentContent, strlen(kAttachmentContent));
283       headers->AddHeader(header);
284       break;
285     case HASH_HEADER_INVALID:
286       header += "BOGUS1==";
287       headers->AddHeader(header);
288       break;
289   }
290   fetcher->set_response_headers(headers);
291 }
292
293 TEST_F(AttachmentDownloaderImplTest, HappyCase) {
294   AttachmentId id1 = AttachmentId::Create();
295   // DownloadAttachment should trigger RequestAccessToken.
296   downloader()->DownloadAttachment(id1, download_callback(id1));
297   RunMessageLoop();
298   // Return valid access token.
299   token_service()->RespondToAccessTokenRequest(
300       GoogleServiceAuthError::AuthErrorNone());
301   RunMessageLoop();
302   // Check that there is outstanding URLFetcher request and complete it.
303   CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
304   // Verify that callback was called for the right id with the right result.
305   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
306 }
307
308 TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) {
309   AttachmentId id1 = AttachmentId::Create();
310   // Call DownloadAttachment two times for the same id.
311   downloader()->DownloadAttachment(id1, download_callback(id1));
312   downloader()->DownloadAttachment(id1, download_callback(id1));
313   RunMessageLoop();
314   // Return valid access token.
315   token_service()->RespondToAccessTokenRequest(
316       GoogleServiceAuthError::AuthErrorNone());
317   RunMessageLoop();
318   // Start one more download after access token is received.
319   downloader()->DownloadAttachment(id1, download_callback(id1));
320   // Complete URLFetcher request.
321   CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
322   // Verify that all download requests completed.
323   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
324   EXPECT_EQ(3, num_completed_downloads());
325
326   // Let's download the same attachment again.
327   downloader()->DownloadAttachment(id1, download_callback(id1));
328   RunMessageLoop();
329   // Verify that it didn't finish prematurely.
330   EXPECT_EQ(3, num_completed_downloads());
331   // Return valid access token.
332   token_service()->RespondToAccessTokenRequest(
333       GoogleServiceAuthError::AuthErrorNone());
334   RunMessageLoop();
335   // Complete URLFetcher request.
336   CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
337   // Verify that all download requests completed.
338   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
339   EXPECT_EQ(4, num_completed_downloads());
340 }
341
342 TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) {
343   AttachmentId id1 = AttachmentId::Create();
344   AttachmentId id2 = AttachmentId::Create();
345   // Trigger first RequestAccessToken.
346   downloader()->DownloadAttachment(id1, download_callback(id1));
347   RunMessageLoop();
348   // Return valid access token.
349   token_service()->RespondToAccessTokenRequest(
350       GoogleServiceAuthError::AuthErrorNone());
351   RunMessageLoop();
352   // Trigger second RequestAccessToken.
353   downloader()->DownloadAttachment(id2, download_callback(id2));
354   RunMessageLoop();
355   // Fail RequestAccessToken.
356   token_service()->RespondToAccessTokenRequest(
357       GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
358   RunMessageLoop();
359   // Only id2 should fail.
360   VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
361   // Complete request for id1.
362   CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
363   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
364 }
365
366 TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) {
367   AttachmentId id1 = AttachmentId::Create();
368   downloader()->DownloadAttachment(id1, download_callback(id1));
369   RunMessageLoop();
370   // Return valid access token.
371   token_service()->RespondToAccessTokenRequest(
372       GoogleServiceAuthError::AuthErrorNone());
373   RunMessageLoop();
374   // Fail URLFetcher. This should trigger download failure and access token
375   // invalidation.
376   CompleteDownload(net::HTTP_UNAUTHORIZED, HASH_HEADER_VALID);
377   EXPECT_EQ(1, token_service()->num_invalidate_token());
378   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
379 }
380
381 TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) {
382   AttachmentId id1 = AttachmentId::Create();
383   downloader()->DownloadAttachment(id1, download_callback(id1));
384   RunMessageLoop();
385   // Return valid access token.
386   token_service()->RespondToAccessTokenRequest(
387       GoogleServiceAuthError::AuthErrorNone());
388   RunMessageLoop();
389   // Fail URLFetcher. This should trigger download failure. Access token
390   // shouldn't be invalidated.
391   CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE, HASH_HEADER_VALID);
392   EXPECT_EQ(0, token_service()->num_invalidate_token());
393   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
394 }
395
396 // Verify that if no hash is present on the response the downloader accepts the
397 // received attachment.
398 TEST_F(AttachmentDownloaderImplTest, NoHash) {
399   AttachmentId id1 = AttachmentId::Create();
400   downloader()->DownloadAttachment(id1, download_callback(id1));
401   RunMessageLoop();
402   token_service()->RespondToAccessTokenRequest(
403       GoogleServiceAuthError::AuthErrorNone());
404   RunMessageLoop();
405   CompleteDownload(net::HTTP_OK, HASH_HEADER_NONE);
406   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
407 }
408
409 // Verify that if an invalid hash is present on the response the downloader
410 // treats it as a transient error.
411 TEST_F(AttachmentDownloaderImplTest, InvalidHash) {
412   AttachmentId id1 = AttachmentId::Create();
413   downloader()->DownloadAttachment(id1, download_callback(id1));
414   RunMessageLoop();
415   token_service()->RespondToAccessTokenRequest(
416       GoogleServiceAuthError::AuthErrorNone());
417   RunMessageLoop();
418   CompleteDownload(net::HTTP_OK, HASH_HEADER_INVALID);
419   VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
420 }
421
422
423 // Verify that extract fails when there is no crc32c value.
424 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_Empty) {
425   std::string raw;
426   raw += "HTTP/1.1 200 OK\n";
427   raw += "Foo: bar\n";
428   raw += "X-Goog-HASH: crc32c=\n";
429   raw += "\n";
430   std::replace(raw.begin(), raw.end(), '\n', '\0');
431   scoped_refptr<net::HttpResponseHeaders> headers(
432       new net::HttpResponseHeaders(raw));
433   std::string extracted;
434   ASSERT_FALSE(AttachmentDownloaderImpl::ExtractCrc32c(*headers, &extracted));
435 }
436
437 // Verify that extract finds the first crc32c and ignores others.
438 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_First) {
439   const std::string expected = "z8SuHQ==";
440   std::string raw;
441   raw += "HTTP/1.1 200 OK\n";
442   raw += "Foo: bar\n";
443   // Ignored because it's the wrong header.
444   raw += "X-Goog-Hashes: crc32c=AAAAAA==\n";
445   // Header name matches.  The md5 item is ignored.
446   raw += "X-Goog-HASH: md5=rL0Y20zC+Fzt72VPzMSk2A==,crc32c=" + expected + "\n";
447   // Ignored because we already found a crc32c in the one above.
448   raw += "X-Goog-HASH: crc32c=AAAAAA==\n";
449   raw += "\n";
450   std::replace(raw.begin(), raw.end(), '\n', '\0');
451   scoped_refptr<net::HttpResponseHeaders> headers(
452       new net::HttpResponseHeaders(raw));
453   std::string extracted;
454   ASSERT_TRUE(AttachmentDownloaderImpl::ExtractCrc32c(*headers, &extracted));
455   ASSERT_EQ(expected, extracted);
456 }
457
458 // Verify that extract fails if there is no crc32c.
459 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_None) {
460   std::string raw;
461   raw += "HTTP/1.1 200 OK\n";
462   raw += "Foo: bar\n";
463   raw += "X-Goog-Hash: md5=rL0Y20zC+Fzt72VPzMSk2A==\n";
464   raw += "\n";
465   std::replace(raw.begin(), raw.end(), '\n', '\0');
466   scoped_refptr<net::HttpResponseHeaders> headers(
467       new net::HttpResponseHeaders(raw));
468   std::string extracted;
469   ASSERT_FALSE(AttachmentDownloaderImpl::ExtractCrc32c(*headers, &extracted));
470 }
471
472 }  // namespace syncer