Refactor AO2TS to make it easier to componentize
[chromium-blink-merge.git] / sync / internal_api / attachments / attachment_downloader_impl_unittest.cc
blobde6ca9a921bb6de82e689f4c24a0e0480aaed22f
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.
5 #include "sync/internal_api/public/attachments/attachment_downloader_impl.h"
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/test/histogram_tester.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "google_apis/gaia/fake_oauth2_token_service.h"
14 #include "google_apis/gaia/gaia_constants.h"
15 #include "net/http/http_response_headers.h"
16 #include "net/url_request/test_url_fetcher_factory.h"
17 #include "net/url_request/url_request_test_util.h"
18 #include "sync/api/attachments/attachment.h"
19 #include "sync/internal_api/public/attachments/attachment_uploader_impl.h"
20 #include "sync/internal_api/public/attachments/attachment_util.h"
21 #include "sync/internal_api/public/base/model_type.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "third_party/leveldatabase/src/util/crc32c.h"
25 namespace syncer {
27 namespace {
29 const char kAccountId[] = "attachments@gmail.com";
30 const char kAccessToken[] = "access.token";
31 const char kAttachmentServerUrl[] = "http://attachments.com/";
32 const char kAttachmentContent[] = "attachment.content";
33 const char kStoreBirthday[] = "z00000000-0000-007b-0000-0000000004d2";
34 const syncer::ModelType kModelType = syncer::ModelType::ARTICLES;
36 // MockOAuth2TokenService remembers last request for access token and verifies
37 // that only one request is active at a time.
38 // Call RespondToAccessTokenRequest to respond to it.
39 class MockOAuth2TokenService : public FakeOAuth2TokenService {
40 public:
41 MockOAuth2TokenService() : num_invalidate_token_(0) {}
43 ~MockOAuth2TokenService() override {}
45 void RespondToAccessTokenRequest(GoogleServiceAuthError error);
47 int num_invalidate_token() const { return num_invalidate_token_; }
49 protected:
50 void FetchOAuth2Token(RequestImpl* request,
51 const std::string& account_id,
52 net::URLRequestContextGetter* getter,
53 const std::string& client_id,
54 const std::string& client_secret,
55 const ScopeSet& scopes) override;
57 void InvalidateAccessTokenImpl(const std::string& account_id,
58 const std::string& client_id,
59 const ScopeSet& scopes,
60 const std::string& access_token) override;
62 private:
63 base::WeakPtr<RequestImpl> last_request_;
64 int num_invalidate_token_;
67 void MockOAuth2TokenService::RespondToAccessTokenRequest(
68 GoogleServiceAuthError error) {
69 EXPECT_TRUE(last_request_ != NULL);
70 std::string access_token;
71 base::Time expiration_time;
72 if (error == GoogleServiceAuthError::AuthErrorNone()) {
73 access_token = kAccessToken;
74 expiration_time = base::Time::Max();
76 base::MessageLoop::current()->PostTask(
77 FROM_HERE,
78 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
79 last_request_,
80 error,
81 access_token,
82 expiration_time));
85 void MockOAuth2TokenService::FetchOAuth2Token(
86 RequestImpl* request,
87 const std::string& account_id,
88 net::URLRequestContextGetter* getter,
89 const std::string& client_id,
90 const std::string& client_secret,
91 const ScopeSet& scopes) {
92 // Only one request at a time is allowed.
93 EXPECT_TRUE(last_request_ == NULL);
94 last_request_ = request->AsWeakPtr();
97 void MockOAuth2TokenService::InvalidateAccessTokenImpl(
98 const std::string& account_id,
99 const std::string& client_id,
100 const ScopeSet& scopes,
101 const std::string& access_token) {
102 ++num_invalidate_token_;
105 class TokenServiceProvider
106 : public OAuth2TokenServiceRequest::TokenServiceProvider,
107 base::NonThreadSafe {
108 public:
109 TokenServiceProvider(OAuth2TokenService* token_service);
111 // OAuth2TokenService::TokenServiceProvider implementation.
112 scoped_refptr<base::SingleThreadTaskRunner> GetTokenServiceTaskRunner()
113 override;
114 OAuth2TokenService* GetTokenService() override;
116 private:
117 ~TokenServiceProvider() override;
119 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
120 OAuth2TokenService* token_service_;
123 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
124 : task_runner_(base::ThreadTaskRunnerHandle::Get()),
125 token_service_(token_service) {
126 DCHECK(token_service_);
129 TokenServiceProvider::~TokenServiceProvider() {
132 scoped_refptr<base::SingleThreadTaskRunner>
133 TokenServiceProvider::GetTokenServiceTaskRunner() {
134 return task_runner_;
137 OAuth2TokenService* TokenServiceProvider::GetTokenService() {
138 DCHECK(task_runner_->BelongsToCurrentThread());
139 return token_service_;
142 } // namespace
144 class AttachmentDownloaderImplTest : public testing::Test {
145 protected:
146 typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult>
147 ResultsMap;
149 enum HashHeaderType {
150 HASH_HEADER_NONE,
151 HASH_HEADER_VALID,
152 HASH_HEADER_INVALID
155 AttachmentDownloaderImplTest()
156 : num_completed_downloads_(0),
157 attachment_id_(
158 Attachment::Create(new base::RefCountedStaticMemory(
159 kAttachmentContent,
160 strlen(kAttachmentContent))).GetId()) {}
162 void SetUp() override;
163 void TearDown() override;
165 AttachmentDownloader* downloader() { return attachment_downloader_.get(); }
167 MockOAuth2TokenService* token_service() { return token_service_.get(); }
169 int num_completed_downloads() { return num_completed_downloads_; }
171 const AttachmentId attachment_id() const { return attachment_id_; }
173 AttachmentDownloader::DownloadCallback download_callback(
174 const AttachmentId& id) {
175 return base::Bind(&AttachmentDownloaderImplTest::DownloadDone,
176 base::Unretained(this),
177 id);
180 // Respond with |response_code| and hash header of type |hash_header_type|.
181 void CompleteDownload(int response_code, HashHeaderType hash_header_type);
183 void DownloadDone(const AttachmentId& attachment_id,
184 const AttachmentDownloader::DownloadResult& result,
185 scoped_ptr<Attachment> attachment);
187 void VerifyDownloadResult(const AttachmentId& attachment_id,
188 const AttachmentDownloader::DownloadResult& result);
190 void RunMessageLoop();
192 private:
193 static void AddHashHeader(HashHeaderType hash_header_type,
194 net::TestURLFetcher* fetcher);
196 base::MessageLoopForIO message_loop_;
197 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
198 net::TestURLFetcherFactory url_fetcher_factory_;
199 scoped_ptr<MockOAuth2TokenService> token_service_;
200 scoped_ptr<AttachmentDownloader> attachment_downloader_;
201 ResultsMap download_results_;
202 int num_completed_downloads_;
203 const AttachmentId attachment_id_;
206 void AttachmentDownloaderImplTest::SetUp() {
207 url_request_context_getter_ =
208 new net::TestURLRequestContextGetter(message_loop_.task_runner());
209 url_fetcher_factory_.set_remove_fetcher_on_delete(true);
210 token_service_.reset(new MockOAuth2TokenService());
211 token_service_->AddAccount(kAccountId);
212 scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>
213 token_service_provider(new TokenServiceProvider(token_service_.get()));
215 OAuth2TokenService::ScopeSet scopes;
216 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
217 attachment_downloader_ = AttachmentDownloader::Create(
218 GURL(kAttachmentServerUrl), url_request_context_getter_, kAccountId,
219 scopes, token_service_provider, std::string(kStoreBirthday), kModelType);
222 void AttachmentDownloaderImplTest::TearDown() {
223 RunMessageLoop();
226 void AttachmentDownloaderImplTest::CompleteDownload(
227 int response_code,
228 HashHeaderType hash_header_type) {
229 // TestURLFetcherFactory remembers last active URLFetcher.
230 net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0);
231 // There should be outstanding url fetch request.
232 EXPECT_TRUE(fetcher != NULL);
233 fetcher->set_status(net::URLRequestStatus());
234 fetcher->set_response_code(response_code);
235 if (response_code == net::HTTP_OK) {
236 fetcher->SetResponseString(kAttachmentContent);
238 AddHashHeader(hash_header_type, fetcher);
240 // Call URLFetcherDelegate.
241 net::URLFetcherDelegate* delegate = fetcher->delegate();
242 delegate->OnURLFetchComplete(fetcher);
243 RunMessageLoop();
244 // Once result is processed URLFetcher should be deleted.
245 fetcher = url_fetcher_factory_.GetFetcherByID(0);
246 EXPECT_TRUE(fetcher == NULL);
249 void AttachmentDownloaderImplTest::DownloadDone(
250 const AttachmentId& attachment_id,
251 const AttachmentDownloader::DownloadResult& result,
252 scoped_ptr<Attachment> attachment) {
253 download_results_.insert(std::make_pair(attachment_id, result));
254 if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) {
255 // Successful download should be accompanied by valid attachment with
256 // matching id and valid data.
257 EXPECT_TRUE(attachment != NULL);
258 EXPECT_EQ(attachment_id, attachment->GetId());
260 scoped_refptr<base::RefCountedMemory> data = attachment->GetData();
261 std::string data_as_string(data->front_as<char>(), data->size());
262 EXPECT_EQ(data_as_string, kAttachmentContent);
263 } else {
264 EXPECT_TRUE(attachment == NULL);
266 ++num_completed_downloads_;
269 void AttachmentDownloaderImplTest::VerifyDownloadResult(
270 const AttachmentId& attachment_id,
271 const AttachmentDownloader::DownloadResult& result) {
272 ResultsMap::const_iterator iter = download_results_.find(attachment_id);
273 EXPECT_TRUE(iter != download_results_.end());
274 EXPECT_EQ(iter->second, result);
277 void AttachmentDownloaderImplTest::RunMessageLoop() {
278 base::RunLoop run_loop;
279 run_loop.RunUntilIdle();
282 void AttachmentDownloaderImplTest::AddHashHeader(
283 HashHeaderType hash_header_type,
284 net::TestURLFetcher* fetcher) {
285 std::string header = "X-Goog-Hash: crc32c=";
286 scoped_refptr<net::HttpResponseHeaders> headers(
287 new net::HttpResponseHeaders(""));
288 switch (hash_header_type) {
289 case HASH_HEADER_NONE:
290 break;
291 case HASH_HEADER_VALID:
292 header += AttachmentUploaderImpl::FormatCrc32cHash(leveldb::crc32c::Value(
293 kAttachmentContent, strlen(kAttachmentContent)));
294 headers->AddHeader(header);
295 break;
296 case HASH_HEADER_INVALID:
297 header += "BOGUS1==";
298 headers->AddHeader(header);
299 break;
301 fetcher->set_response_headers(headers);
304 TEST_F(AttachmentDownloaderImplTest, HappyCase) {
305 AttachmentId id1 = attachment_id();
306 // DownloadAttachment should trigger RequestAccessToken.
307 downloader()->DownloadAttachment(id1, download_callback(id1));
308 RunMessageLoop();
309 // Return valid access token.
310 token_service()->RespondToAccessTokenRequest(
311 GoogleServiceAuthError::AuthErrorNone());
312 RunMessageLoop();
313 // Catch histogram entries.
314 base::HistogramTester histogram_tester;
315 // Check that there is outstanding URLFetcher request and complete it.
316 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
317 // Verify that the response code was logged properly.
318 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
319 net::HTTP_OK, 1);
320 // Verify that callback was called for the right id with the right result.
321 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
324 TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) {
325 AttachmentId id1 = attachment_id();
326 base::HistogramTester histogram_tester;
327 // Call DownloadAttachment two times for the same id.
328 downloader()->DownloadAttachment(id1, download_callback(id1));
329 downloader()->DownloadAttachment(id1, download_callback(id1));
330 RunMessageLoop();
331 // Return valid access token.
332 token_service()->RespondToAccessTokenRequest(
333 GoogleServiceAuthError::AuthErrorNone());
334 RunMessageLoop();
335 // Start one more download after access token is received.
336 downloader()->DownloadAttachment(id1, download_callback(id1));
337 // Complete URLFetcher request.
338 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
339 // Verify that all download requests completed.
340 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
341 EXPECT_EQ(3, num_completed_downloads());
343 // Let's download the same attachment again.
344 downloader()->DownloadAttachment(id1, download_callback(id1));
345 RunMessageLoop();
346 // Verify that it didn't finish prematurely.
347 EXPECT_EQ(3, num_completed_downloads());
348 // Return valid access token.
349 token_service()->RespondToAccessTokenRequest(
350 GoogleServiceAuthError::AuthErrorNone());
351 RunMessageLoop();
352 // Complete URLFetcher request.
353 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
354 // Verify that all download requests completed.
355 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
356 EXPECT_EQ(4, num_completed_downloads());
357 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
358 net::HTTP_OK, 2);
361 TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) {
362 AttachmentId id1 = attachment_id();
363 AttachmentId id2 = AttachmentId::Create(id1.GetSize(), id1.GetCrc32c());
364 // Trigger first RequestAccessToken.
365 downloader()->DownloadAttachment(id1, download_callback(id1));
366 RunMessageLoop();
367 // Return valid access token.
368 token_service()->RespondToAccessTokenRequest(
369 GoogleServiceAuthError::AuthErrorNone());
370 RunMessageLoop();
371 // Trigger second RequestAccessToken.
372 downloader()->DownloadAttachment(id2, download_callback(id2));
373 RunMessageLoop();
374 // Fail RequestAccessToken.
375 token_service()->RespondToAccessTokenRequest(
376 GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
377 RunMessageLoop();
378 // Only id2 should fail.
379 VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
380 // Complete request for id1.
381 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
382 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
385 TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) {
386 AttachmentId id1 = attachment_id();
387 downloader()->DownloadAttachment(id1, download_callback(id1));
388 RunMessageLoop();
389 // Return valid access token.
390 token_service()->RespondToAccessTokenRequest(
391 GoogleServiceAuthError::AuthErrorNone());
392 RunMessageLoop();
393 // Fail URLFetcher. This should trigger download failure and access token
394 // invalidation.
395 base::HistogramTester histogram_tester;
396 CompleteDownload(net::HTTP_UNAUTHORIZED, HASH_HEADER_VALID);
397 EXPECT_EQ(1, token_service()->num_invalidate_token());
398 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
399 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
400 net::HTTP_UNAUTHORIZED, 1);
403 TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) {
404 AttachmentId id1 = attachment_id();
405 downloader()->DownloadAttachment(id1, download_callback(id1));
406 RunMessageLoop();
407 // Return valid access token.
408 token_service()->RespondToAccessTokenRequest(
409 GoogleServiceAuthError::AuthErrorNone());
410 RunMessageLoop();
411 // Fail URLFetcher. This should trigger download failure. Access token
412 // shouldn't be invalidated.
413 base::HistogramTester histogram_tester;
414 CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE, HASH_HEADER_VALID);
415 EXPECT_EQ(0, token_service()->num_invalidate_token());
416 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
417 histogram_tester.ExpectUniqueSample("Sync.Attachments.DownloadResponseCode",
418 net::HTTP_SERVICE_UNAVAILABLE, 1);
421 // Verify that if no hash is present on the response the downloader accepts the
422 // received attachment.
423 TEST_F(AttachmentDownloaderImplTest, NoHash) {
424 AttachmentId id1 = attachment_id();
425 downloader()->DownloadAttachment(id1, download_callback(id1));
426 RunMessageLoop();
427 token_service()->RespondToAccessTokenRequest(
428 GoogleServiceAuthError::AuthErrorNone());
429 RunMessageLoop();
430 CompleteDownload(net::HTTP_OK, HASH_HEADER_NONE);
431 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS);
434 // Verify that if an invalid hash is present on the response the downloader
435 // treats it as a transient error.
436 TEST_F(AttachmentDownloaderImplTest, InvalidHash) {
437 AttachmentId id1 = attachment_id();
438 downloader()->DownloadAttachment(id1, download_callback(id1));
439 RunMessageLoop();
440 token_service()->RespondToAccessTokenRequest(
441 GoogleServiceAuthError::AuthErrorNone());
442 RunMessageLoop();
443 CompleteDownload(net::HTTP_OK, HASH_HEADER_INVALID);
444 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR);
447 // Verify that when the hash from the attachment id does not match the one on
448 // the response the result is an unspecified error.
449 TEST_F(AttachmentDownloaderImplTest, IdHashDoesNotMatch) {
450 // id1 has the wrong crc32c.
451 AttachmentId id1 = AttachmentId::Create(attachment_id().GetSize(), 12345);
452 downloader()->DownloadAttachment(id1, download_callback(id1));
453 RunMessageLoop();
454 token_service()->RespondToAccessTokenRequest(
455 GoogleServiceAuthError::AuthErrorNone());
456 RunMessageLoop();
457 CompleteDownload(net::HTTP_OK, HASH_HEADER_VALID);
458 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_UNSPECIFIED_ERROR);
462 // Verify that extract fails when there is no headers object.
463 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_NoHeaders) {
464 uint32_t extracted;
465 ASSERT_FALSE(AttachmentDownloaderImpl::ExtractCrc32c(nullptr, &extracted));
468 // Verify that extract fails when there is no crc32c value.
469 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_Empty) {
470 std::string raw;
471 raw += "HTTP/1.1 200 OK\n";
472 raw += "Foo: bar\n";
473 raw += "X-Goog-HASH: crc32c=\n";
474 raw += "\n";
475 std::replace(raw.begin(), raw.end(), '\n', '\0');
476 scoped_refptr<net::HttpResponseHeaders> headers(
477 new net::HttpResponseHeaders(raw));
478 uint32_t extracted;
479 ASSERT_FALSE(
480 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
483 // Verify that extract finds the first crc32c and ignores others.
484 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_First) {
485 const std::string expected_encoded = "z8SuHQ==";
486 const uint32_t expected = 3485773341;
487 std::string raw;
488 raw += "HTTP/1.1 200 OK\n";
489 raw += "Foo: bar\n";
490 // Ignored because it's the wrong header.
491 raw += "X-Goog-Hashes: crc32c=AAAAAA==\n";
492 // Header name matches. The md5 item is ignored.
493 raw += "X-Goog-HASH: md5=rL0Y20zC+Fzt72VPzMSk2A==,crc32c=" +
494 expected_encoded + "\n";
495 // Ignored because we already found a crc32c in the one above.
496 raw += "X-Goog-HASH: crc32c=AAAAAA==\n";
497 raw += "\n";
498 std::replace(raw.begin(), raw.end(), '\n', '\0');
499 scoped_refptr<net::HttpResponseHeaders> headers(
500 new net::HttpResponseHeaders(raw));
501 uint32_t extracted;
502 ASSERT_TRUE(
503 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
504 ASSERT_EQ(expected, extracted);
507 // Verify that extract fails when encoded value is too long.
508 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_TooLong) {
509 std::string raw;
510 raw += "HTTP/1.1 200 OK\n";
511 raw += "Foo: bar\n";
512 raw += "X-Goog-HASH: crc32c=AAAAAAAA\n";
513 raw += "\n";
514 std::replace(raw.begin(), raw.end(), '\n', '\0');
515 scoped_refptr<net::HttpResponseHeaders> headers(
516 new net::HttpResponseHeaders(raw));
517 uint32_t extracted;
518 ASSERT_FALSE(
519 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
522 // Verify that extract fails if there is no crc32c.
523 TEST_F(AttachmentDownloaderImplTest, ExtractCrc32c_None) {
524 std::string raw;
525 raw += "HTTP/1.1 200 OK\n";
526 raw += "Foo: bar\n";
527 raw += "X-Goog-Hash: md5=rL0Y20zC+Fzt72VPzMSk2A==\n";
528 raw += "\n";
529 std::replace(raw.begin(), raw.end(), '\n', '\0');
530 scoped_refptr<net::HttpResponseHeaders> headers(
531 new net::HttpResponseHeaders(raw));
532 uint32_t extracted;
533 ASSERT_FALSE(
534 AttachmentDownloaderImpl::ExtractCrc32c(headers.get(), &extracted));
537 } // namespace syncer