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 kAttachments
[] = "/attachments/";
42 using net::test_server::BasicHttpResponse
;
43 using net::test_server::HttpRequest
;
44 using net::test_server::HttpResponse
;
48 // A mock implementation of an OAuth2TokenService.
50 // Use |SetResponse| to vary the response to token requests.
52 // Use |num_invalidate_token| and |last_token_invalidated| to check the number
53 // of invalidate token operations performed and the last token invalidated.
54 class MockOAuth2TokenService
: public FakeOAuth2TokenService
{
56 MockOAuth2TokenService();
57 virtual ~MockOAuth2TokenService();
59 void SetResponse(const GoogleServiceAuthError
& error
,
60 const std::string
& access_token
,
61 const base::Time
& expiration
);
63 int num_invalidate_token() const { return num_invalidate_token_
; }
65 const std::string
& last_token_invalidated() const {
66 return last_token_invalidated_
;
70 virtual void FetchOAuth2Token(RequestImpl
* request
,
71 const std::string
& account_id
,
72 net::URLRequestContextGetter
* getter
,
73 const std::string
& client_id
,
74 const std::string
& client_secret
,
75 const ScopeSet
& scopes
) OVERRIDE
;
77 virtual void InvalidateOAuth2Token(const std::string
& account_id
,
78 const std::string
& client_id
,
79 const ScopeSet
& scopes
,
80 const std::string
& access_token
) OVERRIDE
;
83 GoogleServiceAuthError response_error_
;
84 std::string response_access_token_
;
85 base::Time response_expiration_
;
86 int num_invalidate_token_
;
87 std::string last_token_invalidated_
;
90 MockOAuth2TokenService::MockOAuth2TokenService()
91 : response_error_(GoogleServiceAuthError::AuthErrorNone()),
92 response_access_token_(kAccessToken
),
93 response_expiration_(base::Time::Max()),
94 num_invalidate_token_(0) {
97 MockOAuth2TokenService::~MockOAuth2TokenService() {
100 void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError
& error
,
101 const std::string
& access_token
,
102 const base::Time
& expiration
) {
103 response_error_
= error
;
104 response_access_token_
= access_token
;
105 response_expiration_
= expiration
;
108 void MockOAuth2TokenService::FetchOAuth2Token(
109 RequestImpl
* request
,
110 const std::string
& account_id
,
111 net::URLRequestContextGetter
* getter
,
112 const std::string
& client_id
,
113 const std::string
& client_secret
,
114 const ScopeSet
& scopes
) {
115 base::MessageLoop::current()->PostTask(
117 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer
,
118 request
->AsWeakPtr(),
120 response_access_token_
,
121 response_expiration_
));
124 void MockOAuth2TokenService::InvalidateOAuth2Token(
125 const std::string
& account_id
,
126 const std::string
& client_id
,
127 const ScopeSet
& scopes
,
128 const std::string
& access_token
) {
129 ++num_invalidate_token_
;
130 last_token_invalidated_
= access_token
;
133 class TokenServiceProvider
134 : public OAuth2TokenServiceRequest::TokenServiceProvider
,
135 base::NonThreadSafe
{
137 TokenServiceProvider(OAuth2TokenService
* token_service
);
139 // OAuth2TokenService::TokenServiceProvider implementation.
140 virtual scoped_refptr
<base::SingleThreadTaskRunner
>
141 GetTokenServiceTaskRunner() OVERRIDE
;
142 virtual OAuth2TokenService
* GetTokenService() OVERRIDE
;
145 virtual ~TokenServiceProvider();
147 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner_
;
148 OAuth2TokenService
* token_service_
;
151 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService
* token_service
)
152 : task_runner_(base::ThreadTaskRunnerHandle::Get()),
153 token_service_(token_service
) {
154 DCHECK(token_service_
);
157 TokenServiceProvider::~TokenServiceProvider() {
160 scoped_refptr
<base::SingleThreadTaskRunner
>
161 TokenServiceProvider::GetTokenServiceTaskRunner() {
165 OAuth2TokenService
* TokenServiceProvider::GetTokenService() {
166 DCHECK(task_runner_
->BelongsToCurrentThread());
167 return token_service_
;
170 // Text fixture for AttachmentUploaderImpl test.
172 // This fixture provides an embedded HTTP server and a mock OAuth2 token service
173 // for interacting with AttachmentUploaderImpl
174 class AttachmentUploaderImplTest
: public testing::Test
,
175 public base::NonThreadSafe
{
177 void OnRequestReceived(const HttpRequest
& request
);
180 AttachmentUploaderImplTest();
181 virtual void SetUp();
182 virtual void TearDown();
184 // Run the message loop until UploadDone has been invoked |num_uploads| times.
185 void RunAndWaitFor(int num_uploads
);
187 scoped_ptr
<AttachmentUploader
>& uploader();
188 const AttachmentUploader::UploadCallback
& upload_callback() const;
189 std::vector
<HttpRequest
>& http_requests_received();
190 std::vector
<AttachmentUploader::UploadResult
>& upload_results();
191 std::vector
<AttachmentId
>& attachment_ids();
192 MockOAuth2TokenService
& token_service();
193 base::MessageLoopForIO
& message_loop();
194 RequestHandler
& request_handler();
197 // An UploadCallback invoked by AttachmentUploaderImpl.
198 void UploadDone(const AttachmentUploader::UploadResult
& result
,
199 const AttachmentId
& attachment_id
);
201 base::MessageLoopForIO message_loop_
;
202 scoped_refptr
<net::URLRequestContextGetter
> url_request_context_getter_
;
203 scoped_ptr
<RequestHandler
> request_handler_
;
204 scoped_ptr
<AttachmentUploader
> uploader_
;
205 AttachmentUploader::UploadCallback upload_callback_
;
206 net::test_server::EmbeddedTestServer server_
;
207 // A closure that signals an upload has finished.
208 base::Closure signal_upload_done_
;
209 std::vector
<HttpRequest
> http_requests_received_
;
210 std::vector
<AttachmentUploader::UploadResult
> upload_results_
;
211 std::vector
<AttachmentId
> attachment_ids_
;
212 scoped_ptr
<MockOAuth2TokenService
> token_service_
;
214 // Must be last data member.
215 base::WeakPtrFactory
<AttachmentUploaderImplTest
> weak_ptr_factory_
;
218 // Handles HTTP requests received by the EmbeddedTestServer.
220 // Responds with HTTP_OK by default. See |SetStatusCode|.
221 class RequestHandler
: public base::NonThreadSafe
{
223 // Construct a RequestHandler that will PostTask to |test| using
224 // |test_task_runner|.
226 const scoped_refptr
<base::SingleThreadTaskRunner
>& test_task_runner
,
227 const base::WeakPtr
<AttachmentUploaderImplTest
>& test
);
231 scoped_ptr
<HttpResponse
> HandleRequest(const HttpRequest
& request
);
233 // Set the HTTP status code to respond with.
234 void SetStatusCode(const net::HttpStatusCode
& status_code
);
236 // Returns the HTTP status code that will be used in responses.
237 net::HttpStatusCode
GetStatusCode() const;
240 // Protects status_code_.
241 mutable base::Lock mutex_
;
242 net::HttpStatusCode status_code_
;
244 scoped_refptr
<base::SingleThreadTaskRunner
> test_task_runner_
;
245 base::WeakPtr
<AttachmentUploaderImplTest
> test_
;
248 AttachmentUploaderImplTest::AttachmentUploaderImplTest()
249 : weak_ptr_factory_(this) {
252 void AttachmentUploaderImplTest::OnRequestReceived(const HttpRequest
& request
) {
253 DCHECK(CalledOnValidThread());
254 http_requests_received_
.push_back(request
);
257 void AttachmentUploaderImplTest::SetUp() {
258 DCHECK(CalledOnValidThread());
259 request_handler_
.reset(new RequestHandler(message_loop_
.message_loop_proxy(),
260 weak_ptr_factory_
.GetWeakPtr()));
261 url_request_context_getter_
=
262 new net::TestURLRequestContextGetter(message_loop_
.message_loop_proxy());
264 ASSERT_TRUE(server_
.InitializeAndWaitUntilReady());
265 server_
.RegisterRequestHandler(
266 base::Bind(&RequestHandler::HandleRequest
,
267 base::Unretained(request_handler_
.get())));
269 GURL
url(base::StringPrintf("http://localhost:%d/", server_
.port()));
271 token_service_
.reset(new MockOAuth2TokenService
);
272 scoped_refptr
<OAuth2TokenServiceRequest::TokenServiceProvider
>
273 token_service_provider(new TokenServiceProvider(token_service_
.get()));
275 OAuth2TokenService::ScopeSet scopes
;
276 scopes
.insert(GaiaConstants::kChromeSyncOAuth2Scope
);
277 uploader().reset(new AttachmentUploaderImpl(url
,
278 url_request_context_getter_
,
281 token_service_provider
));
283 upload_callback_
= base::Bind(&AttachmentUploaderImplTest::UploadDone
,
284 base::Unretained(this));
287 void AttachmentUploaderImplTest::TearDown() {
288 base::RunLoop().RunUntilIdle();
291 void AttachmentUploaderImplTest::RunAndWaitFor(int num_uploads
) {
292 for (int i
= 0; i
< num_uploads
; ++i
) {
293 // Run the loop until one upload completes.
294 base::RunLoop run_loop
;
295 signal_upload_done_
= run_loop
.QuitClosure();
300 scoped_ptr
<AttachmentUploader
>& AttachmentUploaderImplTest::uploader() {
304 const AttachmentUploader::UploadCallback
&
305 AttachmentUploaderImplTest::upload_callback() const {
306 return upload_callback_
;
309 std::vector
<HttpRequest
>& AttachmentUploaderImplTest::http_requests_received() {
310 return http_requests_received_
;
313 std::vector
<AttachmentUploader::UploadResult
>&
314 AttachmentUploaderImplTest::upload_results() {
315 return upload_results_
;
318 std::vector
<AttachmentId
>&
319 AttachmentUploaderImplTest::attachment_ids() {
320 return attachment_ids_
;
323 MockOAuth2TokenService
& AttachmentUploaderImplTest::token_service() {
324 return *token_service_
;
327 base::MessageLoopForIO
& AttachmentUploaderImplTest::message_loop() {
328 return message_loop_
;
331 RequestHandler
& AttachmentUploaderImplTest::request_handler() {
332 return *request_handler_
;
335 void AttachmentUploaderImplTest::UploadDone(
336 const AttachmentUploader::UploadResult
& result
,
337 const AttachmentId
& attachment_id
) {
338 DCHECK(CalledOnValidThread());
339 upload_results_
.push_back(result
);
340 attachment_ids_
.push_back(attachment_id
);
341 DCHECK(!signal_upload_done_
.is_null());
342 signal_upload_done_
.Run();
345 RequestHandler::RequestHandler(
346 const scoped_refptr
<base::SingleThreadTaskRunner
>& test_task_runner
,
347 const base::WeakPtr
<AttachmentUploaderImplTest
>& test
)
348 : status_code_(net::HTTP_OK
),
349 test_task_runner_(test_task_runner
),
354 RequestHandler::~RequestHandler() {
358 scoped_ptr
<HttpResponse
> RequestHandler::HandleRequest(
359 const HttpRequest
& request
) {
360 DCHECK(CalledOnValidThread());
361 test_task_runner_
->PostTask(
364 &AttachmentUploaderImplTest::OnRequestReceived
, test_
, request
));
365 scoped_ptr
<BasicHttpResponse
> response(new BasicHttpResponse
);
366 response
->set_code(GetStatusCode());
367 response
->set_content_type("text/plain");
368 return response
.PassAs
<HttpResponse
>();
371 void RequestHandler::SetStatusCode(const net::HttpStatusCode
& status_code
) {
372 base::AutoLock
lock(mutex_
);
373 status_code_
= status_code
;
376 net::HttpStatusCode
RequestHandler::GetStatusCode() const {
377 base::AutoLock
lock(mutex_
);
381 TEST_F(AttachmentUploaderImplTest
, GetURLForAttachmentId_NoPath
) {
382 AttachmentId id
= AttachmentId::Create();
383 std::string unique_id
= id
.GetProto().unique_id();
384 GURL
sync_service_url("https://example.com");
385 EXPECT_EQ("https://example.com/attachments/" + unique_id
,
386 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url
, id
)
390 TEST_F(AttachmentUploaderImplTest
, GetURLForAttachmentId_JustSlash
) {
391 AttachmentId id
= AttachmentId::Create();
392 std::string unique_id
= id
.GetProto().unique_id();
393 GURL
sync_service_url("https://example.com/");
394 EXPECT_EQ("https://example.com/attachments/" + unique_id
,
395 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url
, id
)
399 TEST_F(AttachmentUploaderImplTest
, GetURLForAttachmentId_Path
) {
400 AttachmentId id
= AttachmentId::Create();
401 std::string unique_id
= id
.GetProto().unique_id();
402 GURL
sync_service_url("https://example.com/service");
403 EXPECT_EQ("https://example.com/service/attachments/" + unique_id
,
404 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url
, id
)
408 TEST_F(AttachmentUploaderImplTest
, GetURLForAttachmentId_PathAndSlash
) {
409 AttachmentId id
= AttachmentId::Create();
410 std::string unique_id
= id
.GetProto().unique_id();
411 GURL
sync_service_url("https://example.com/service/");
412 EXPECT_EQ("https://example.com/service/attachments/" + unique_id
,
413 AttachmentUploaderImpl::GetURLForAttachmentId(sync_service_url
, id
)
417 // Verify the "happy case" of uploading an attachment.
419 // Token is requested, token is returned, HTTP request is made, attachment is
420 // received by server.
421 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_HappyCase
) {
422 token_service().AddAccount(kAccountId
);
423 request_handler().SetStatusCode(net::HTTP_OK
);
425 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
426 some_data
->data() = kAttachmentData
;
427 Attachment attachment
= Attachment::Create(some_data
);
428 uploader()->UploadAttachment(attachment
, upload_callback());
430 // Run until the done callback is invoked.
433 // See that the done callback was invoked with the right arguments.
434 ASSERT_EQ(1U, upload_results().size());
435 EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS
, upload_results()[0]);
436 ASSERT_EQ(1U, attachment_ids().size());
437 EXPECT_EQ(attachment
.GetId(), attachment_ids()[0]);
439 // See that the HTTP server received one request.
440 ASSERT_EQ(1U, http_requests_received().size());
441 const HttpRequest
& http_request
= http_requests_received().front();
442 EXPECT_EQ(net::test_server::METHOD_POST
, http_request
.method
);
443 std::string
expected_relative_url(kAttachments
+
444 attachment
.GetId().GetProto().unique_id());
445 EXPECT_EQ(expected_relative_url
, http_request
.relative_url
);
446 EXPECT_TRUE(http_request
.has_content
);
447 EXPECT_EQ(kAttachmentData
, http_request
.content
);
448 const std::string
header_name(kAuthorization
);
449 const std::string
header_value(std::string("Bearer ") + kAccessToken
);
450 EXPECT_THAT(http_request
.headers
,
451 testing::Contains(testing::Pair(header_name
, header_value
)));
454 // Verify two overlapping calls to upload the same attachment result in only one
456 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_Collapse
) {
457 token_service().AddAccount(kAccountId
);
458 request_handler().SetStatusCode(net::HTTP_OK
);
460 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
461 some_data
->data() = kAttachmentData
;
462 Attachment attachment1
= Attachment::Create(some_data
);
463 Attachment attachment2
= attachment1
;
464 uploader()->UploadAttachment(attachment1
, upload_callback());
465 uploader()->UploadAttachment(attachment2
, upload_callback());
467 // Wait for upload_callback() to be invoked twice.
469 // See there was only one request.
470 EXPECT_EQ(1U, http_requests_received().size());
473 // Verify that the internal state associated with an upload is removed when the
474 // uplaod finishes. We do this by issuing two non-overlapping uploads for the
475 // same attachment and see that it results in two HTTP requests.
476 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_CleanUpAfterUpload
) {
477 token_service().AddAccount(kAccountId
);
478 request_handler().SetStatusCode(net::HTTP_OK
);
480 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
481 some_data
->data() = kAttachmentData
;
482 Attachment attachment1
= Attachment::Create(some_data
);
483 Attachment attachment2
= attachment1
;
484 uploader()->UploadAttachment(attachment1
, upload_callback());
486 // Wait for upload_callback() to be invoked before starting the second upload.
488 uploader()->UploadAttachment(attachment2
, upload_callback());
490 // Wait for upload_callback() to be invoked a second time.
492 // See there were two requests.
493 ASSERT_EQ(2U, http_requests_received().size());
496 // Verify that we do not issue an HTTP request when we fail to receive an access
499 // Token is requested, no token is returned, no HTTP request is made
500 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_FailToGetToken
) {
501 // Note, we won't receive a token because we did not add kAccountId to the
503 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
504 some_data
->data() = kAttachmentData
;
505 Attachment attachment
= Attachment::Create(some_data
);
506 uploader()->UploadAttachment(attachment
, upload_callback());
510 // See that the done callback was invoked.
511 ASSERT_EQ(1U, upload_results().size());
512 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR
, upload_results()[0]);
513 ASSERT_EQ(1U, attachment_ids().size());
514 EXPECT_EQ(attachment
.GetId(), attachment_ids()[0]);
516 // See that no HTTP request was received.
517 ASSERT_EQ(0U, http_requests_received().size());
520 // Verify behavior when the server returns "503 Service Unavailable".
521 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_ServiceUnavilable
) {
522 token_service().AddAccount(kAccountId
);
523 request_handler().SetStatusCode(net::HTTP_SERVICE_UNAVAILABLE
);
525 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
526 some_data
->data() = kAttachmentData
;
527 Attachment attachment
= Attachment::Create(some_data
);
528 uploader()->UploadAttachment(attachment
, upload_callback());
532 // See that the done callback was invoked.
533 ASSERT_EQ(1U, upload_results().size());
534 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR
, upload_results()[0]);
535 ASSERT_EQ(1U, attachment_ids().size());
536 EXPECT_EQ(attachment
.GetId(), attachment_ids()[0]);
538 // See that the HTTP server received one request.
539 ASSERT_EQ(1U, http_requests_received().size());
540 const HttpRequest
& http_request
= http_requests_received().front();
541 EXPECT_EQ(net::test_server::METHOD_POST
, http_request
.method
);
542 std::string
expected_relative_url(kAttachments
+
543 attachment
.GetId().GetProto().unique_id());
544 EXPECT_EQ(expected_relative_url
, http_request
.relative_url
);
545 EXPECT_TRUE(http_request
.has_content
);
546 EXPECT_EQ(kAttachmentData
, http_request
.content
);
547 std::string
expected_header(kAuthorization
);
548 const std::string
header_name(kAuthorization
);
549 const std::string
header_value(std::string("Bearer ") + kAccessToken
);
550 EXPECT_THAT(http_request
.headers
,
551 testing::Contains(testing::Pair(header_name
, header_value
)));
553 // See that we did not invalidate the token.
554 ASSERT_EQ(0, token_service().num_invalidate_token());
557 // Verify that when we receive an "401 Unauthorized" we invalidate the access
559 TEST_F(AttachmentUploaderImplTest
, UploadAttachment_BadToken
) {
560 token_service().AddAccount(kAccountId
);
561 request_handler().SetStatusCode(net::HTTP_UNAUTHORIZED
);
563 scoped_refptr
<base::RefCountedString
> some_data(new base::RefCountedString
);
564 some_data
->data() = kAttachmentData
;
565 Attachment attachment
= Attachment::Create(some_data
);
566 uploader()->UploadAttachment(attachment
, upload_callback());
570 // See that the done callback was invoked.
571 ASSERT_EQ(1U, upload_results().size());
572 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR
, upload_results()[0]);
573 ASSERT_EQ(1U, attachment_ids().size());
574 EXPECT_EQ(attachment
.GetId(), attachment_ids()[0]);
576 // See that the HTTP server received one request.
577 ASSERT_EQ(1U, http_requests_received().size());
578 const HttpRequest
& http_request
= http_requests_received().front();
579 EXPECT_EQ(net::test_server::METHOD_POST
, http_request
.method
);
580 std::string
expected_relative_url(kAttachments
+
581 attachment
.GetId().GetProto().unique_id());
582 EXPECT_EQ(expected_relative_url
, http_request
.relative_url
);
583 EXPECT_TRUE(http_request
.has_content
);
584 EXPECT_EQ(kAttachmentData
, http_request
.content
);
585 std::string
expected_header(kAuthorization
);
586 const std::string
header_name(kAuthorization
);
587 const std::string
header_value(std::string("Bearer ") + kAccessToken
);
588 EXPECT_THAT(http_request
.headers
,
589 testing::Contains(testing::Pair(header_name
, header_value
)));
591 // See that we invalidated the token.
592 ASSERT_EQ(1, token_service().num_invalidate_token());
595 // TODO(maniscalco): Add test case for when we are uploading an attachment that
596 // already exists. 409 Conflict? (bug 379825)
598 } // namespace syncer