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_uploader_impl.h" 6 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" 29 30 namespace { 31 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 37 } // namespace 38 39 namespace syncer { 40 41 using net::test_server::BasicHttpResponse; 42 using net::test_server::HttpRequest; 43 using net::test_server::HttpResponse; 44 45 class RequestHandler; 46 47 // A mock implementation of an OAuth2TokenService. 48 // 49 // Use |SetResponse| to vary the response to token requests. 50 // 51 // Use |num_invalidate_token| and |last_token_invalidated| to check the number 52 // of invalidate token operations performed and the last token invalidated. 53 class MockOAuth2TokenService : public FakeOAuth2TokenService { 54 public: 55 MockOAuth2TokenService(); 56 virtual ~MockOAuth2TokenService(); 57 58 void SetResponse(const GoogleServiceAuthError& error, 59 const std::string& access_token, 60 const base::Time& expiration); 61 62 int num_invalidate_token() const { return num_invalidate_token_; } 63 64 const std::string& last_token_invalidated() const { 65 return last_token_invalidated_; 66 } 67 68 protected: 69 virtual void FetchOAuth2Token(RequestImpl* request, 70 const std::string& account_id, 71 net::URLRequestContextGetter* getter, 72 const std::string& client_id, 73 const std::string& client_secret, 74 const ScopeSet& scopes) OVERRIDE; 75 76 virtual void InvalidateOAuth2Token(const std::string& account_id, 77 const std::string& client_id, 78 const ScopeSet& scopes, 79 const std::string& access_token) OVERRIDE; 80 81 private: 82 GoogleServiceAuthError response_error_; 83 std::string response_access_token_; 84 base::Time response_expiration_; 85 int num_invalidate_token_; 86 std::string last_token_invalidated_; 87 }; 88 89 MockOAuth2TokenService::MockOAuth2TokenService() 90 : response_error_(GoogleServiceAuthError::AuthErrorNone()), 91 response_access_token_(kAccessToken), 92 response_expiration_(base::Time::Max()), 93 num_invalidate_token_(0) { 94 } 95 96 MockOAuth2TokenService::~MockOAuth2TokenService() { 97 } 98 99 void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError& error, 100 const std::string& access_token, 101 const base::Time& expiration) { 102 response_error_ = error; 103 response_access_token_ = access_token; 104 response_expiration_ = expiration; 105 } 106 107 void MockOAuth2TokenService::FetchOAuth2Token( 108 RequestImpl* request, 109 const std::string& account_id, 110 net::URLRequestContextGetter* getter, 111 const std::string& client_id, 112 const std::string& client_secret, 113 const ScopeSet& scopes) { 114 base::MessageLoop::current()->PostTask( 115 FROM_HERE, 116 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer, 117 request->AsWeakPtr(), 118 response_error_, 119 response_access_token_, 120 response_expiration_)); 121 } 122 123 void MockOAuth2TokenService::InvalidateOAuth2Token( 124 const std::string& account_id, 125 const std::string& client_id, 126 const ScopeSet& scopes, 127 const std::string& access_token) { 128 ++num_invalidate_token_; 129 last_token_invalidated_ = access_token; 130 } 131 132 class TokenServiceProvider 133 : public OAuth2TokenServiceRequest::TokenServiceProvider, 134 base::NonThreadSafe { 135 public: 136 TokenServiceProvider(OAuth2TokenService* token_service); 137 virtual ~TokenServiceProvider(); 138 139 // OAuth2TokenService::TokenServiceProvider implementation. 140 virtual scoped_refptr<base::SingleThreadTaskRunner> 141 GetTokenServiceTaskRunner() OVERRIDE; 142 virtual OAuth2TokenService* GetTokenService() OVERRIDE; 143 144 private: 145 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 146 OAuth2TokenService* token_service_; 147 }; 148 149 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service) 150 : task_runner_(base::ThreadTaskRunnerHandle::Get()), 151 token_service_(token_service) { 152 DCHECK(token_service_); 153 } 154 155 TokenServiceProvider::~TokenServiceProvider() { 156 } 157 158 scoped_refptr<base::SingleThreadTaskRunner> 159 TokenServiceProvider::GetTokenServiceTaskRunner() { 160 return task_runner_; 161 } 162 163 OAuth2TokenService* TokenServiceProvider::GetTokenService() { 164 DCHECK(task_runner_->BelongsToCurrentThread()); 165 return token_service_; 166 } 167 168 // Text fixture for AttachmentUploaderImpl test. 169 // 170 // This fixture provides an embedded HTTP server and a mock OAuth2 token service 171 // for interacting with AttachmentUploaderImpl 172 class AttachmentUploaderImplTest : public testing::Test, 173 public base::NonThreadSafe { 174 public: 175 void OnRequestReceived(const HttpRequest& request); 176 177 protected: 178 AttachmentUploaderImplTest(); 179 virtual void SetUp(); 180 virtual void TearDown(); 181 182 // Run the message loop until UploadDone has been invoked |num_uploads| times. 183 void RunAndWaitFor(int num_uploads); 184 185 scoped_ptr<AttachmentUploader>& uploader(); 186 const AttachmentUploader::UploadCallback& upload_callback() const; 187 std::vector<HttpRequest>& http_requests_received(); 188 std::vector<AttachmentUploader::UploadResult>& upload_results(); 189 std::vector<AttachmentId>& updated_attachment_ids(); 190 MockOAuth2TokenService& token_service(); 191 base::MessageLoopForIO& message_loop(); 192 RequestHandler& request_handler(); 193 194 private: 195 // An UploadCallback invoked by AttachmentUploaderImpl. 196 void UploadDone(const AttachmentUploader::UploadResult& result, 197 const AttachmentId& updated_attachment_id); 198 199 base::MessageLoopForIO message_loop_; 200 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; 201 scoped_ptr<RequestHandler> request_handler_; 202 scoped_ptr<AttachmentUploader> uploader_; 203 AttachmentUploader::UploadCallback upload_callback_; 204 net::test_server::EmbeddedTestServer server_; 205 // A closure that signals an upload has finished. 206 base::Closure signal_upload_done_; 207 std::vector<HttpRequest> http_requests_received_; 208 std::vector<AttachmentUploader::UploadResult> upload_results_; 209 std::vector<AttachmentId> updated_attachment_ids_; 210 scoped_ptr<MockOAuth2TokenService> token_service_; 211 212 // Must be last data member. 213 base::WeakPtrFactory<AttachmentUploaderImplTest> weak_ptr_factory_; 214 }; 215 216 // Handles HTTP requests received by the EmbeddedTestServer. 217 // 218 // Responds with HTTP_OK by default. See |SetStatusCode|. 219 class RequestHandler : public base::NonThreadSafe { 220 public: 221 // Construct a RequestHandler that will PostTask to |test| using 222 // |test_task_runner|. 223 RequestHandler( 224 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner, 225 const base::WeakPtr<AttachmentUploaderImplTest>& test); 226 227 ~RequestHandler(); 228 229 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request); 230 231 // Set the HTTP status code to respond with. 232 void SetStatusCode(const net::HttpStatusCode& status_code); 233 234 // Returns the HTTP status code that will be used in responses. 235 net::HttpStatusCode GetStatusCode() const; 236 237 private: 238 // Protects status_code_. 239 mutable base::Lock mutex_; 240 net::HttpStatusCode status_code_; 241 242 scoped_refptr<base::SingleThreadTaskRunner> test_task_runner_; 243 base::WeakPtr<AttachmentUploaderImplTest> test_; 244 }; 245 246 AttachmentUploaderImplTest::AttachmentUploaderImplTest() 247 : weak_ptr_factory_(this) { 248 } 249 250 void AttachmentUploaderImplTest::OnRequestReceived(const HttpRequest& request) { 251 DCHECK(CalledOnValidThread()); 252 http_requests_received_.push_back(request); 253 } 254 255 void AttachmentUploaderImplTest::SetUp() { 256 DCHECK(CalledOnValidThread()); 257 request_handler_.reset(new RequestHandler(message_loop_.message_loop_proxy(), 258 weak_ptr_factory_.GetWeakPtr())); 259 url_request_context_getter_ = 260 new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy()); 261 262 ASSERT_TRUE(server_.InitializeAndWaitUntilReady()); 263 server_.RegisterRequestHandler( 264 base::Bind(&RequestHandler::HandleRequest, 265 base::Unretained(request_handler_.get()))); 266 267 std::string url_prefix( 268 base::StringPrintf("http://localhost:%d/uploads/", server_.port())); 269 270 token_service_.reset(new MockOAuth2TokenService); 271 scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider> 272 token_service_provider(new TokenServiceProvider(token_service_.get())); 273 274 OAuth2TokenService::ScopeSet scopes; 275 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); 276 uploader().reset(new AttachmentUploaderImpl(url_prefix, 277 url_request_context_getter_, 278 kAccountId, 279 scopes, 280 token_service_provider.Pass())); 281 282 upload_callback_ = base::Bind(&AttachmentUploaderImplTest::UploadDone, 283 base::Unretained(this)); 284 } 285 286 void AttachmentUploaderImplTest::TearDown() { 287 base::RunLoop().RunUntilIdle(); 288 } 289 290 void AttachmentUploaderImplTest::RunAndWaitFor(int num_uploads) { 291 for (int i = 0; i < num_uploads; ++i) { 292 // Run the loop until one upload completes. 293 base::RunLoop run_loop; 294 signal_upload_done_ = run_loop.QuitClosure(); 295 run_loop.Run(); 296 } 297 } 298 299 scoped_ptr<AttachmentUploader>& AttachmentUploaderImplTest::uploader() { 300 return uploader_; 301 } 302 303 const AttachmentUploader::UploadCallback& 304 AttachmentUploaderImplTest::upload_callback() const { 305 return upload_callback_; 306 } 307 308 std::vector<HttpRequest>& AttachmentUploaderImplTest::http_requests_received() { 309 return http_requests_received_; 310 } 311 312 std::vector<AttachmentUploader::UploadResult>& 313 AttachmentUploaderImplTest::upload_results() { 314 return upload_results_; 315 } 316 317 std::vector<AttachmentId>& 318 AttachmentUploaderImplTest::updated_attachment_ids() { 319 return updated_attachment_ids_; 320 } 321 322 MockOAuth2TokenService& AttachmentUploaderImplTest::token_service() { 323 return *token_service_; 324 } 325 326 base::MessageLoopForIO& AttachmentUploaderImplTest::message_loop() { 327 return message_loop_; 328 } 329 330 RequestHandler& AttachmentUploaderImplTest::request_handler() { 331 return *request_handler_; 332 } 333 334 void AttachmentUploaderImplTest::UploadDone( 335 const AttachmentUploader::UploadResult& result, 336 const AttachmentId& updated_attachment_id) { 337 DCHECK(CalledOnValidThread()); 338 upload_results_.push_back(result); 339 updated_attachment_ids_.push_back(updated_attachment_id); 340 DCHECK(!signal_upload_done_.is_null()); 341 signal_upload_done_.Run(); 342 } 343 344 RequestHandler::RequestHandler( 345 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner, 346 const base::WeakPtr<AttachmentUploaderImplTest>& test) 347 : status_code_(net::HTTP_OK), 348 test_task_runner_(test_task_runner), 349 test_(test) { 350 DetachFromThread(); 351 } 352 353 RequestHandler::~RequestHandler() { 354 DetachFromThread(); 355 } 356 357 scoped_ptr<HttpResponse> RequestHandler::HandleRequest( 358 const HttpRequest& request) { 359 DCHECK(CalledOnValidThread()); 360 test_task_runner_->PostTask( 361 FROM_HERE, 362 base::Bind( 363 &AttachmentUploaderImplTest::OnRequestReceived, test_, request)); 364 scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse); 365 response->set_code(GetStatusCode()); 366 response->set_content_type("text/plain"); 367 return response.PassAs<HttpResponse>(); 368 } 369 370 void RequestHandler::SetStatusCode(const net::HttpStatusCode& status_code) { 371 base::AutoLock lock(mutex_); 372 status_code_ = status_code; 373 } 374 375 net::HttpStatusCode RequestHandler::GetStatusCode() const { 376 base::AutoLock lock(mutex_); 377 return status_code_; 378 } 379 380 // Verify the "happy case" of uploading an attachment. 381 // 382 // Token is requested, token is returned, HTTP request is made, attachment is 383 // received by server. 384 TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) { 385 token_service().AddAccount(kAccountId); 386 request_handler().SetStatusCode(net::HTTP_OK); 387 388 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 389 some_data->data() = kAttachmentData; 390 Attachment attachment = Attachment::Create(some_data); 391 uploader()->UploadAttachment(attachment, upload_callback()); 392 393 // Run until the done callback is invoked. 394 RunAndWaitFor(1); 395 396 // See that the done callback was invoked with the right arguments. 397 ASSERT_EQ(1U, upload_results().size()); 398 EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results()[0]); 399 ASSERT_EQ(1U, updated_attachment_ids().size()); 400 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); 401 402 // See that the HTTP server received one request. 403 ASSERT_EQ(1U, http_requests_received().size()); 404 const HttpRequest& http_request = http_requests_received().front(); 405 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); 406 std::string expected_relative_url("/uploads/" + 407 attachment.GetId().GetProto().unique_id()); 408 EXPECT_EQ(expected_relative_url, http_request.relative_url); 409 EXPECT_TRUE(http_request.has_content); 410 EXPECT_EQ(kAttachmentData, http_request.content); 411 const std::string header_name(kAuthorization); 412 const std::string header_value(std::string("Bearer ") + kAccessToken); 413 EXPECT_THAT(http_request.headers, 414 testing::Contains(testing::Pair(header_name, header_value))); 415 416 // TODO(maniscalco): Once AttachmentUploaderImpl is capable of updating the 417 // AttachmentId with server address information about the attachment, add some 418 // checks here to verify it works properly (bug 371522). 419 } 420 421 // Verify two overlapping calls to upload the same attachment result in only one 422 // HTTP request. 423 TEST_F(AttachmentUploaderImplTest, UploadAttachment_Collapse) { 424 token_service().AddAccount(kAccountId); 425 request_handler().SetStatusCode(net::HTTP_OK); 426 427 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 428 some_data->data() = kAttachmentData; 429 Attachment attachment1 = Attachment::Create(some_data); 430 Attachment attachment2 = attachment1; 431 uploader()->UploadAttachment(attachment1, upload_callback()); 432 uploader()->UploadAttachment(attachment2, upload_callback()); 433 434 // Wait for upload_callback() to be invoked twice. 435 RunAndWaitFor(2); 436 // See there was only one request. 437 EXPECT_EQ(1U, http_requests_received().size()); 438 } 439 440 // Verify that the internal state associated with an upload is removed when the 441 // uplaod finishes. We do this by issuing two non-overlapping uploads for the 442 // same attachment and see that it results in two HTTP requests. 443 TEST_F(AttachmentUploaderImplTest, UploadAttachment_CleanUpAfterUpload) { 444 token_service().AddAccount(kAccountId); 445 request_handler().SetStatusCode(net::HTTP_OK); 446 447 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 448 some_data->data() = kAttachmentData; 449 Attachment attachment1 = Attachment::Create(some_data); 450 Attachment attachment2 = attachment1; 451 uploader()->UploadAttachment(attachment1, upload_callback()); 452 453 // Wait for upload_callback() to be invoked before starting the second upload. 454 RunAndWaitFor(1); 455 uploader()->UploadAttachment(attachment2, upload_callback()); 456 457 // Wait for upload_callback() to be invoked a second time. 458 RunAndWaitFor(1); 459 // See there were two requests. 460 ASSERT_EQ(2U, http_requests_received().size()); 461 } 462 463 // Verify that we do not issue an HTTP request when we fail to receive an access 464 // token. 465 // 466 // Token is requested, no token is returned, no HTTP request is made 467 TEST_F(AttachmentUploaderImplTest, UploadAttachment_FailToGetToken) { 468 // Note, we won't receive a token because we did not add kAccountId to the 469 // token service. 470 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 471 some_data->data() = kAttachmentData; 472 Attachment attachment = Attachment::Create(some_data); 473 uploader()->UploadAttachment(attachment, upload_callback()); 474 475 RunAndWaitFor(1); 476 477 // See that the done callback was invoked. 478 ASSERT_EQ(1U, upload_results().size()); 479 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); 480 ASSERT_EQ(1U, updated_attachment_ids().size()); 481 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); 482 483 // See that no HTTP request was received. 484 ASSERT_EQ(0U, http_requests_received().size()); 485 } 486 487 // Verify behavior when the server returns "503 Service Unavailable". 488 TEST_F(AttachmentUploaderImplTest, UploadAttachment_ServiceUnavilable) { 489 token_service().AddAccount(kAccountId); 490 request_handler().SetStatusCode(net::HTTP_SERVICE_UNAVAILABLE); 491 492 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 493 some_data->data() = kAttachmentData; 494 Attachment attachment = Attachment::Create(some_data); 495 uploader()->UploadAttachment(attachment, upload_callback()); 496 497 RunAndWaitFor(1); 498 499 // See that the done callback was invoked. 500 ASSERT_EQ(1U, upload_results().size()); 501 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); 502 ASSERT_EQ(1U, updated_attachment_ids().size()); 503 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); 504 505 // See that the HTTP server received one request. 506 ASSERT_EQ(1U, http_requests_received().size()); 507 const HttpRequest& http_request = http_requests_received().front(); 508 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); 509 std::string expected_relative_url("/uploads/" + 510 attachment.GetId().GetProto().unique_id()); 511 EXPECT_EQ(expected_relative_url, http_request.relative_url); 512 EXPECT_TRUE(http_request.has_content); 513 EXPECT_EQ(kAttachmentData, http_request.content); 514 std::string expected_header(kAuthorization); 515 const std::string header_name(kAuthorization); 516 const std::string header_value(std::string("Bearer ") + kAccessToken); 517 EXPECT_THAT(http_request.headers, 518 testing::Contains(testing::Pair(header_name, header_value))); 519 520 // See that we did not invalidate the token. 521 ASSERT_EQ(0, token_service().num_invalidate_token()); 522 } 523 524 // Verify that when we receive an "401 Unauthorized" we invalidate the access 525 // token. 526 TEST_F(AttachmentUploaderImplTest, UploadAttachment_BadToken) { 527 token_service().AddAccount(kAccountId); 528 request_handler().SetStatusCode(net::HTTP_UNAUTHORIZED); 529 530 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); 531 some_data->data() = kAttachmentData; 532 Attachment attachment = Attachment::Create(some_data); 533 uploader()->UploadAttachment(attachment, upload_callback()); 534 535 RunAndWaitFor(1); 536 537 // See that the done callback was invoked. 538 ASSERT_EQ(1U, upload_results().size()); 539 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); 540 ASSERT_EQ(1U, updated_attachment_ids().size()); 541 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); 542 543 // See that the HTTP server received one request. 544 ASSERT_EQ(1U, http_requests_received().size()); 545 const HttpRequest& http_request = http_requests_received().front(); 546 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); 547 std::string expected_relative_url("/uploads/" + 548 attachment.GetId().GetProto().unique_id()); 549 EXPECT_EQ(expected_relative_url, http_request.relative_url); 550 EXPECT_TRUE(http_request.has_content); 551 EXPECT_EQ(kAttachmentData, http_request.content); 552 std::string expected_header(kAuthorization); 553 const std::string header_name(kAuthorization); 554 const std::string header_value(std::string("Bearer ") + kAccessToken); 555 EXPECT_THAT(http_request.headers, 556 testing::Contains(testing::Pair(header_name, header_value))); 557 558 // See that we invalidated the token. 559 ASSERT_EQ(1, token_service().num_invalidate_token()); 560 } 561 562 // TODO(maniscalco): Add test case for when we are uploading an attachment that 563 // already exists. 409 Conflict? (bug 379825) 564 565 } // namespace syncer 566