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_uploader_impl.h"
8 #include "base/callback.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/memory/ref_counted_memory.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/synchronization/lock.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/non_thread_safe.h"
17 #include "base/threading/thread.h"
18 #include "google_apis/gaia/fake_oauth2_token_service.h"
19 #include "google_apis/gaia/gaia_constants.h"
20 #include "google_apis/gaia/oauth2_token_service_request.h"
21 #include "net/test/embedded_test_server/embedded_test_server.h"
22 #include "net/test/embedded_test_server/http_request.h"
23 #include "net/test/embedded_test_server/http_response.h"
24 #include "net/url_request/url_request_test_util.h"
25 #include "sync/api/attachments/attachment.h"
26 #include "sync/protocol/sync.pb.h"
27 #include "testing/gmock/include/gmock/gmock-matchers.h"
28 #include "testing/gtest/include/gtest/gtest.h"
32 const char kAttachmentData[] = "some data";
33 const char kAccountId[] = "some-account-id";
34 const char kAccessToken[] = "some-access-token";
35 const char kAuthorization[] = "Authorization";
36 const char kContentLength[] = "Content-Length";
37 const char kContentType[] = "Content-Type";
38 const char kContentTypeValue[] = "application/octet-stream";
39 const char kXGoogHash[] = "X-Goog-Hash";
40 const char kAttachments[] = "/attachments/";
46 using net::test_server::BasicHttpResponse;
47 using net::test_server::HttpRequest;
48 using net::test_server::HttpResponse;
52 // A mock implementation of an OAuth2TokenService.
54 // Use |SetResponse| to vary the response to token requests.
56 // Use |num_invalidate_token| and |last_token_invalidated| to check the number
57 // of invalidate token operations performed and the last token invalidated.
58 class MockOAuth2TokenService : public FakeOAuth2TokenService {
60 MockOAuth2TokenService();
61 ~MockOAuth2TokenService() override;
63 void SetResponse(const GoogleServiceAuthError& error,
64 const std::string& access_token,
65 const base::Time& expiration);
67 int num_invalidate_token() const { return num_invalidate_token_; }
69 const std::string& last_token_invalidated() const {
70 return last_token_invalidated_;
74 void FetchOAuth2Token(RequestImpl* request,
75 const std::string& account_id,
76 net::URLRequestContextGetter* getter,
77 const std::string& client_id,
78 const std::string& client_secret,
79 const ScopeSet& scopes) override;
81 void InvalidateOAuth2Token(const std::string& account_id,
82 const std::string& client_id,
83 const ScopeSet& scopes,
84 const std::string& access_token) override;
87 GoogleServiceAuthError response_error_;
88 std::string response_access_token_;
89 base::Time response_expiration_;
90 int num_invalidate_token_;
91 std::string last_token_invalidated_;
94 MockOAuth2TokenService::MockOAuth2TokenService()
95 : response_error_(GoogleServiceAuthError::AuthErrorNone()),
96 response_access_token_(kAccessToken),
97 response_expiration_(base::Time::Max()),
98 num_invalidate_token_(0) {
101 MockOAuth2TokenService::~MockOAuth2TokenService() {
104 void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError& error,
105 const std::string& access_token,
106 const base::Time& expiration) {
107 response_error_ = error;
108 response_access_token_ = access_token;
109 response_expiration_ = expiration;
112 void MockOAuth2TokenService::FetchOAuth2Token(
113 RequestImpl* request,
114 const std::string& account_id,
115 net::URLRequestContextGetter* getter,
116 const std::string& client_id,
117 const std::string& client_secret,
118 const ScopeSet& scopes) {
119 base::MessageLoop::current()->PostTask(
121 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
122 request->AsWeakPtr(),
124 response_access_token_,
125 response_expiration_));
128 void MockOAuth2TokenService::InvalidateOAuth2Token(
129 const std::string& account_id,
130 const std::string& client_id,
131 const ScopeSet& scopes,
132 const std::string& access_token) {
133 ++num_invalidate_token_;
134 last_token_invalidated_ = access_token;
137 class TokenServiceProvider
138 : public OAuth2TokenServiceRequest::TokenServiceProvider,
139 base::NonThreadSafe {
141 TokenServiceProvider(OAuth2TokenService* token_service);
143 // OAuth2TokenService::TokenServiceProvider implementation.
144 scoped_refptr<base::SingleThreadTaskRunner> GetTokenServiceTaskRunner()
146 OAuth2TokenService* GetTokenService() override;
149 ~TokenServiceProvider() override;
151 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
152 OAuth2TokenService* token_service_;
155 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
156 : task_runner_(base::ThreadTaskRunnerHandle::Get()),
157 token_service_(token_service) {
158 DCHECK(token_service_);
161 TokenServiceProvider::~TokenServiceProvider() {
164 scoped_refptr<base::SingleThreadTaskRunner>
165 TokenServiceProvider::GetTokenServiceTaskRunner() {
169 OAuth2TokenService* TokenServiceProvider::GetTokenService() {
170 DCHECK(task_runner_->BelongsToCurrentThread());
171 return token_service_;
174 // Text fixture for AttachmentUploaderImpl test.
176 // This fixture provides an embedded HTTP server and a mock OAuth2 token service
177 // for interacting with AttachmentUploaderImpl
178 class AttachmentUploaderImplTest : public testing::Test,
179 public base::NonThreadSafe {
181 void OnRequestReceived(const HttpRequest& request);
184 AttachmentUploaderImplTest();
185 virtual void SetUp();
186 virtual void TearDown();
188 // Run the message loop until UploadDone has been invoked |num_uploads| times.
189 void RunAndWaitFor(int num_uploads);
191 // Upload an attachment and have the server respond with |status_code|.
193 // Returns the attachment that was uploaded.
194 Attachment UploadAndRespondWith(const net::HttpStatusCode& status_code);
196 scoped_ptr<AttachmentUploader>& uploader();
197 const AttachmentUploader::UploadCallback& upload_callback() const;
198 std::vector<HttpRequest>& http_requests_received();
199 std::vector<AttachmentUploader::UploadResult>& upload_results();
200 std::vector<AttachmentId>& attachment_ids();
201 MockOAuth2TokenService& token_service();
202 base::MessageLoopForIO& message_loop();
203 RequestHandler& request_handler();
206 // An UploadCallback invoked by AttachmentUploaderImpl.
207 void UploadDone(const AttachmentUploader::UploadResult& result,
208 const AttachmentId& attachment_id);
210 base::MessageLoopForIO message_loop_;
211 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
212 scoped_ptr<RequestHandler> request_handler_;
213 scoped_ptr<AttachmentUploader> uploader_;
214 AttachmentUploader::UploadCallback upload_callback_;
215 net::test_server::EmbeddedTestServer server_;
216 // A closure that signals an upload has finished.
217 base::Closure signal_upload_done_;
218 std::vector<HttpRequest> http_requests_received_;
219 std::vector<AttachmentUploader::UploadResult> upload_results_;
220 std::vector<AttachmentId> attachment_ids_;
221 scoped_ptr<MockOAuth2TokenService> token_service_;
223 // Must be last data member.
224 base::WeakPtrFactory<AttachmentUploaderImplTest> weak_ptr_factory_;
227 // Handles HTTP requests received by the EmbeddedTestServer.
229 // Responds with HTTP_OK by default. See |SetStatusCode|.
230 class RequestHandler : public base::NonThreadSafe {
232 // Construct a RequestHandler that will PostTask to |test| using
233 // |test_task_runner|.
235 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner,
236 const base::WeakPtr<AttachmentUploaderImplTest>& test);
240 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
242 // Set the HTTP status code to respond with.
243 void SetStatusCode(const net::HttpStatusCode& status_code);
245 // Returns the HTTP status code that will be used in responses.
246 net::HttpStatusCode GetStatusCode() const;
249 // Protects status_code_.
250 mutable base::Lock mutex_;
251 net::HttpStatusCode status_code_;
253 scoped_refptr<base::SingleThreadTaskRunner> test_task_runner_;
254 base::WeakPtr<AttachmentUploaderImplTest> test_;
257 AttachmentUploaderImplTest::AttachmentUploaderImplTest()
258 : weak_ptr_factory_(this) {
261 void AttachmentUploaderImplTest::OnRequestReceived(const HttpRequest& request) {
262 DCHECK(CalledOnValidThread());
263 http_requests_received_.push_back(request);
266 void AttachmentUploaderImplTest::SetUp() {
267 DCHECK(CalledOnValidThread());
268 request_handler_.reset(new RequestHandler(message_loop_.message_loop_proxy(),
269 weak_ptr_factory_.GetWeakPtr()));
270 url_request_context_getter_ =
271 new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy());
273 ASSERT_TRUE(server_.InitializeAndWaitUntilReady());
274 server_.RegisterRequestHandler(
275 base::Bind(&RequestHandler::HandleRequest,
276 base::Unretained(request_handler_.get())));
278 GURL url(base::StringPrintf("http://localhost:%d/", server_.port()));
280 token_service_.reset(new MockOAuth2TokenService);
281 scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider>
282 token_service_provider(new TokenServiceProvider(token_service_.get()));
284 OAuth2TokenService::ScopeSet scopes;
285 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
286 uploader().reset(new AttachmentUploaderImpl(url,
287 url_request_context_getter_,
290 token_service_provider));
292 upload_callback_ = base::Bind(&AttachmentUploaderImplTest::UploadDone,
293 base::Unretained(this));
296 void AttachmentUploaderImplTest::TearDown() {
297 base::RunLoop().RunUntilIdle();
300 void AttachmentUploaderImplTest::RunAndWaitFor(int num_uploads) {
301 for (int i = 0; i < num_uploads; ++i) {
302 // Run the loop until one upload completes.
303 base::RunLoop run_loop;
304 signal_upload_done_ = run_loop.QuitClosure();
309 Attachment AttachmentUploaderImplTest::UploadAndRespondWith(
310 const net::HttpStatusCode& status_code) {
311 token_service().AddAccount(kAccountId);
312 request_handler().SetStatusCode(status_code);
313 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
314 some_data->data() = kAttachmentData;
315 Attachment attachment = Attachment::Create(some_data);
316 uploader()->UploadAttachment(attachment, upload_callback());
320 scoped_ptr<AttachmentUploader>& AttachmentUploaderImplTest::uploader() {
324 const AttachmentUploader::UploadCallback&
325 AttachmentUploaderImplTest::upload_callback() const {
326 return upload_callback_;
329 std::vector<HttpRequest>& AttachmentUploaderImplTest::http_requests_received() {
330 return http_requests_received_;
333 std::vector<AttachmentUploader::UploadResult>&
334 AttachmentUploaderImplTest::upload_results() {
335 return upload_results_;
338 std::vector<AttachmentId>&
339 AttachmentUploaderImplTest::attachment_ids() {
340 return attachment_ids_;
343 MockOAuth2TokenService& AttachmentUploaderImplTest::token_service() {
344 return *token_service_;
347 base::MessageLoopForIO& AttachmentUploaderImplTest::message_loop() {
348 return message_loop_;
351 RequestHandler& AttachmentUploaderImplTest::request_handler() {
352 return *request_handler_;
355 void AttachmentUploaderImplTest::UploadDone(
356 const AttachmentUploader::UploadResult& result,
357 const AttachmentId& attachment_id) {
358 DCHECK(CalledOnValidThread());
359 upload_results_.push_back(result);
360 attachment_ids_.push_back(attachment_id);
361 DCHECK(!signal_upload_done_.is_null());
362 signal_upload_done_.Run();
365 RequestHandler::RequestHandler(
366 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner,
367 const base::WeakPtr<AttachmentUploaderImplTest>& test)
368 : status_code_(net::HTTP_OK),
369 test_task_runner_(test_task_runner),
374 RequestHandler::~RequestHandler() {
378 scoped_ptr<HttpResponse> RequestHandler::HandleRequest(
379 const HttpRequest& request) {
380 DCHECK(CalledOnValidThread());
381 test_task_runner_->PostTask(
384 &AttachmentUploaderImplTest::OnRequestReceived, test_, request));
385 scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse);
386 response->set_code(GetStatusCode());
387 response->set_content_type("text/plain");
388 return response.Pass();
391 void RequestHandler::SetStatusCode(const net::HttpStatusCode& status_code) {
392 base::AutoLock lock(mutex_);
393 status_code_ = status_code;
396 net::HttpStatusCode RequestHandler::GetStatusCode() const {
397 base::AutoLock lock(mutex_);
401 TEST_F(AttachmentUploaderImplTest, GetURLForAttachmentId_NoPath) {
402 AttachmentId id = AttachmentId::Create();
403 std::string unique_id = id.GetProto().unique_id();
404 GURL sync_service_url("https://example.com");
405 EXPECT_EQ("https://example.com/attachments/" + unique_id,
406 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url, id)
410 TEST_F(AttachmentUploaderImplTest, GetURLForAttachmentId_JustSlash) {
411 AttachmentId id = AttachmentId::Create();
412 std::string unique_id = id.GetProto().unique_id();
413 GURL sync_service_url("https://example.com/");
414 EXPECT_EQ("https://example.com/attachments/" + unique_id,
415 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url, id)
419 TEST_F(AttachmentUploaderImplTest, GetURLForAttachmentId_Path) {
420 AttachmentId id = AttachmentId::Create();
421 std::string unique_id = id.GetProto().unique_id();
422 GURL sync_service_url("https://example.com/service");
423 EXPECT_EQ("https://example.com/service/attachments/" + unique_id,
424 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url, id)
428 TEST_F(AttachmentUploaderImplTest, GetURLForAttachmentId_PathAndSlash) {
429 AttachmentId id = AttachmentId::Create();
430 std::string unique_id = id.GetProto().unique_id();
431 GURL sync_service_url("https://example.com/service/");
432 EXPECT_EQ("https://example.com/service/attachments/" + unique_id,
433 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url, id)
437 // Verify the "happy case" of uploading an attachment.
439 // Token is requested, token is returned, HTTP request is made, attachment is
440 // received by server.
441 TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) {
442 Attachment attachment = UploadAndRespondWith(net::HTTP_OK);
444 // Run until the done callback is invoked.
447 // See that the done callback was invoked with the right arguments.
448 ASSERT_EQ(1U, upload_results().size());
449 EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results()[0]);
450 ASSERT_EQ(1U, attachment_ids().size());
451 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
453 // See that the HTTP server received one request.
454 ASSERT_EQ(1U, http_requests_received().size());
455 const HttpRequest& http_request = http_requests_received().front();
456 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
457 std::string expected_relative_url(kAttachments +
458 attachment.GetId().GetProto().unique_id());
459 EXPECT_EQ(expected_relative_url, http_request.relative_url);
460 EXPECT_TRUE(http_request.has_content);
461 EXPECT_EQ(kAttachmentData, http_request.content);
464 // Verify the request contains the appropriate headers.
465 TEST_F(AttachmentUploaderImplTest, UploadAttachment_Headers) {
466 Attachment attachment = UploadAndRespondWith(net::HTTP_OK);
468 // Run until the done callback is invoked.
471 // See that the done callback was invoked with the right arguments.
472 ASSERT_EQ(1U, upload_results().size());
473 EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results()[0]);
474 ASSERT_EQ(1U, attachment_ids().size());
475 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
477 // See that the HTTP server received one request.
478 ASSERT_EQ(1U, http_requests_received().size());
479 const HttpRequest& http_request = http_requests_received().front();
481 const std::string auth_header_name(kAuthorization);
482 const std::string auth_header_value(std::string("Bearer ") + kAccessToken);
485 http_request.headers,
486 testing::Contains(testing::Pair(kAuthorization, auth_header_value)));
487 EXPECT_THAT(http_request.headers,
488 testing::Contains(testing::Key(kContentLength)));
490 http_request.headers,
491 testing::Contains(testing::Pair(kContentType, kContentTypeValue)));
492 EXPECT_THAT(http_request.headers,
493 testing::Contains(testing::Key(kXGoogHash)));
496 // Verify two overlapping calls to upload the same attachment result in only one
498 TEST_F(AttachmentUploaderImplTest, UploadAttachment_Collapse) {
499 Attachment attachment1 = UploadAndRespondWith(net::HTTP_OK);
500 Attachment attachment2 = attachment1;
501 uploader()->UploadAttachment(attachment2, upload_callback());
503 // Wait for upload_callback() to be invoked twice.
505 // See there was only one request.
506 EXPECT_EQ(1U, http_requests_received().size());
509 // Verify that the internal state associated with an upload is removed when the
510 // uplaod finishes. We do this by issuing two non-overlapping uploads for the
511 // same attachment and see that it results in two HTTP requests.
512 TEST_F(AttachmentUploaderImplTest, UploadAttachment_CleanUpAfterUpload) {
513 Attachment attachment1 = UploadAndRespondWith(net::HTTP_OK);
515 // Wait for upload_callback() to be invoked before starting the second upload.
518 Attachment attachment2 = attachment1;
519 uploader()->UploadAttachment(attachment2, upload_callback());
521 // Wait for upload_callback() to be invoked a second time.
523 // See there were two requests.
524 ASSERT_EQ(2U, http_requests_received().size());
527 // Verify that we do not issue an HTTP request when we fail to receive an access
530 // Token is requested, no token is returned, no HTTP request is made
531 TEST_F(AttachmentUploaderImplTest, UploadAttachment_FailToGetToken) {
532 // Note, we won't receive a token because we did not add kAccountId to the
534 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
535 some_data->data() = kAttachmentData;
536 Attachment attachment = Attachment::Create(some_data);
537 uploader()->UploadAttachment(attachment, upload_callback());
541 // See that the done callback was invoked.
542 ASSERT_EQ(1U, upload_results().size());
543 EXPECT_EQ(AttachmentUploader::UPLOAD_TRANSIENT_ERROR, upload_results()[0]);
544 ASSERT_EQ(1U, attachment_ids().size());
545 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
547 // See that no HTTP request was received.
548 ASSERT_EQ(0U, http_requests_received().size());
551 // Verify behavior when the server returns "503 Service Unavailable".
552 TEST_F(AttachmentUploaderImplTest, UploadAttachment_ServiceUnavilable) {
553 Attachment attachment = UploadAndRespondWith(net::HTTP_SERVICE_UNAVAILABLE);
557 // See that the done callback was invoked.
558 ASSERT_EQ(1U, upload_results().size());
559 EXPECT_EQ(AttachmentUploader::UPLOAD_TRANSIENT_ERROR, upload_results()[0]);
560 ASSERT_EQ(1U, attachment_ids().size());
561 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
563 // See that the HTTP server received one request.
564 ASSERT_EQ(1U, http_requests_received().size());
565 const HttpRequest& http_request = http_requests_received().front();
566 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
567 std::string expected_relative_url(kAttachments +
568 attachment.GetId().GetProto().unique_id());
569 EXPECT_EQ(expected_relative_url, http_request.relative_url);
570 EXPECT_TRUE(http_request.has_content);
571 EXPECT_EQ(kAttachmentData, http_request.content);
573 // See that we did not invalidate the token.
574 ASSERT_EQ(0, token_service().num_invalidate_token());
577 // Verify that we "403 Forbidden" as a non-transient error.
578 TEST_F(AttachmentUploaderImplTest, UploadAttachment_Forbidden) {
579 Attachment attachment = UploadAndRespondWith(net::HTTP_FORBIDDEN);
583 // See that the done callback was invoked.
584 ASSERT_EQ(1U, upload_results().size());
585 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]);
586 ASSERT_EQ(1U, attachment_ids().size());
587 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
589 // See that the HTTP server received one request.
590 ASSERT_EQ(1U, http_requests_received().size());
591 const HttpRequest& http_request = http_requests_received().front();
592 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
593 std::string expected_relative_url(kAttachments +
594 attachment.GetId().GetProto().unique_id());
595 EXPECT_EQ(expected_relative_url, http_request.relative_url);
596 EXPECT_TRUE(http_request.has_content);
597 EXPECT_EQ(kAttachmentData, http_request.content);
599 // See that we did not invalidate the token.
600 ASSERT_EQ(0, token_service().num_invalidate_token());
603 // Verify that when we receive an "401 Unauthorized" we invalidate the access
605 TEST_F(AttachmentUploaderImplTest, UploadAttachment_BadToken) {
606 Attachment attachment = UploadAndRespondWith(net::HTTP_UNAUTHORIZED);
610 // See that the done callback was invoked.
611 ASSERT_EQ(1U, upload_results().size());
612 EXPECT_EQ(AttachmentUploader::UPLOAD_TRANSIENT_ERROR, upload_results()[0]);
613 ASSERT_EQ(1U, attachment_ids().size());
614 EXPECT_EQ(attachment.GetId(), attachment_ids()[0]);
616 // See that the HTTP server received one request.
617 ASSERT_EQ(1U, http_requests_received().size());
618 const HttpRequest& http_request = http_requests_received().front();
619 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
620 std::string expected_relative_url(kAttachments +
621 attachment.GetId().GetProto().unique_id());
622 EXPECT_EQ(expected_relative_url, http_request.relative_url);
623 EXPECT_TRUE(http_request.has_content);
624 EXPECT_EQ(kAttachmentData, http_request.content);
626 // See that we invalidated the token.
627 ASSERT_EQ(1, token_service().num_invalidate_token());
630 TEST_F(AttachmentUploaderImplTest, ComputeCrc32cHash) {
631 scoped_refptr<base::RefCountedString> empty(new base::RefCountedString);
633 EXPECT_EQ("AAAAAA==",
634 AttachmentUploaderImpl::ComputeCrc32cHash(empty->front_as<char>(),
637 scoped_refptr<base::RefCountedString> hello_world(new base::RefCountedString);
638 hello_world->data() = "hello world";
639 EXPECT_EQ("yZRlqg==",
640 AttachmentUploaderImpl::ComputeCrc32cHash(
641 hello_world->front_as<char>(), hello_world->size()));
644 // TODO(maniscalco): Add test case for when we are uploading an attachment that
645 // already exists. 409 Conflict? (bug 379825)
647 } // namespace syncer