Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / sync / internal_api / attachments / attachment_uploader_impl_unittest.cc
blob9cfc625b3b8247893ab2618a87ec8093090dbca6
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"
7 #include "base/bind.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"
30 namespace {
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/";
38 } // namespace
40 namespace syncer {
42 using net::test_server::BasicHttpResponse;
43 using net::test_server::HttpRequest;
44 using net::test_server::HttpResponse;
46 class RequestHandler;
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 {
55 public:
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_;
69 protected:
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;
82 private:
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(
116 FROM_HERE,
117 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
118 request->AsWeakPtr(),
119 response_error_,
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 {
136 public:
137 TokenServiceProvider(OAuth2TokenService* token_service);
139 // OAuth2TokenService::TokenServiceProvider implementation.
140 virtual scoped_refptr<base::SingleThreadTaskRunner>
141 GetTokenServiceTaskRunner() OVERRIDE;
142 virtual OAuth2TokenService* GetTokenService() OVERRIDE;
144 private:
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() {
162 return task_runner_;
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 {
176 public:
177 void OnRequestReceived(const HttpRequest& request);
179 protected:
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();
196 private:
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 {
222 public:
223 // Construct a RequestHandler that will PostTask to |test| using
224 // |test_task_runner|.
225 RequestHandler(
226 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner,
227 const base::WeakPtr<AttachmentUploaderImplTest>& test);
229 ~RequestHandler();
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;
239 private:
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_,
279 kAccountId,
280 scopes,
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();
296 run_loop.Run();
300 scoped_ptr<AttachmentUploader>& AttachmentUploaderImplTest::uploader() {
301 return 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),
350 test_(test) {
351 DetachFromThread();
354 RequestHandler::~RequestHandler() {
355 DetachFromThread();
358 scoped_ptr<HttpResponse> RequestHandler::HandleRequest(
359 const HttpRequest& request) {
360 DCHECK(CalledOnValidThread());
361 test_task_runner_->PostTask(
362 FROM_HERE,
363 base::Bind(
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_);
378 return status_code_;
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)
387 .spec());
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)
396 .spec());
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)
405 .spec());
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)
414 .spec());
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.
431 RunAndWaitFor(1);
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
455 // HTTP request.
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.
468 RunAndWaitFor(2);
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.
487 RunAndWaitFor(1);
488 uploader()->UploadAttachment(attachment2, upload_callback());
490 // Wait for upload_callback() to be invoked a second time.
491 RunAndWaitFor(1);
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
497 // token.
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
502 // token service.
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());
508 RunAndWaitFor(1);
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());
530 RunAndWaitFor(1);
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
558 // token.
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());
568 RunAndWaitFor(1);
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