1// Copyright (c) 2012 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 "chrome/browser/drive/drive_uploader.h" 6 7#include <string> 8#include <vector> 9 10#include "base/bind.h" 11#include "base/files/scoped_temp_dir.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/message_loop/message_loop.h" 14#include "base/run_loop.h" 15#include "base/values.h" 16#include "chrome/browser/drive/dummy_drive_service.h" 17#include "content/public/test/test_browser_thread_bundle.h" 18#include "google_apis/drive/drive_api_parser.h" 19#include "google_apis/drive/test_util.h" 20#include "testing/gtest/include/gtest/gtest.h" 21 22using google_apis::CancelCallback; 23using google_apis::FileResource; 24using google_apis::GDataErrorCode; 25using google_apis::GDATA_NO_CONNECTION; 26using google_apis::GDATA_OTHER_ERROR; 27using google_apis::HTTP_CONFLICT; 28using google_apis::HTTP_CREATED; 29using google_apis::HTTP_NOT_FOUND; 30using google_apis::HTTP_PRECONDITION; 31using google_apis::HTTP_RESUME_INCOMPLETE; 32using google_apis::HTTP_SUCCESS; 33using google_apis::InitiateUploadCallback; 34using google_apis::ProgressCallback; 35using google_apis::UploadRangeResponse; 36using google_apis::drive::UploadRangeCallback; 37namespace test_util = google_apis::test_util; 38 39namespace drive { 40 41namespace { 42 43const char kTestDummyMd5[] = "dummy_md5"; 44const char kTestDocumentTitle[] = "Hello world"; 45const char kTestInitiateUploadParentResourceId[] = "parent_resource_id"; 46const char kTestInitiateUploadResourceId[] = "resource_id"; 47const char kTestMimeType[] = "text/plain"; 48const char kTestUploadNewFileURL[] = "http://test/upload_location/new_file"; 49const char kTestUploadExistingFileURL[] = 50 "http://test/upload_location/existing_file"; 51const int64 kUploadChunkSize = 512 * 1024; 52const char kTestETag[] = "test_etag"; 53 54// Mock DriveService that verifies if the uploaded content matches the preset 55// expectation. 56class MockDriveServiceWithUploadExpectation : public DummyDriveService { 57 public: 58 // Sets up an expected upload content. InitiateUpload and ResumeUpload will 59 // verify that the specified data is correctly uploaded. 60 MockDriveServiceWithUploadExpectation( 61 const base::FilePath& expected_upload_file, 62 int64 expected_content_length) 63 : expected_upload_file_(expected_upload_file), 64 expected_content_length_(expected_content_length), 65 received_bytes_(0), 66 resume_upload_call_count_(0) {} 67 68 int64 received_bytes() const { return received_bytes_; } 69 void set_received_bytes(int64 received_bytes) { 70 received_bytes_ = received_bytes; 71 } 72 73 int64 resume_upload_call_count() const { return resume_upload_call_count_; } 74 75 private: 76 // DriveServiceInterface overrides. 77 // Handles a request for obtaining an upload location URL. 78 virtual CancelCallback InitiateUploadNewFile( 79 const std::string& content_type, 80 int64 content_length, 81 const std::string& parent_resource_id, 82 const std::string& title, 83 const InitiateUploadNewFileOptions& options, 84 const InitiateUploadCallback& callback) OVERRIDE { 85 EXPECT_EQ(kTestDocumentTitle, title); 86 EXPECT_EQ(kTestMimeType, content_type); 87 EXPECT_EQ(expected_content_length_, content_length); 88 EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id); 89 90 // Calls back the upload URL for subsequent ResumeUpload requests. 91 // InitiateUpload is an asynchronous function, so don't callback directly. 92 base::MessageLoop::current()->PostTask(FROM_HERE, 93 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL))); 94 return CancelCallback(); 95 } 96 97 virtual CancelCallback InitiateUploadExistingFile( 98 const std::string& content_type, 99 int64 content_length, 100 const std::string& resource_id, 101 const InitiateUploadExistingFileOptions& options, 102 const InitiateUploadCallback& callback) OVERRIDE { 103 EXPECT_EQ(kTestMimeType, content_type); 104 EXPECT_EQ(expected_content_length_, content_length); 105 EXPECT_EQ(kTestInitiateUploadResourceId, resource_id); 106 107 if (!options.etag.empty() && options.etag != kTestETag) { 108 base::MessageLoop::current()->PostTask(FROM_HERE, 109 base::Bind(callback, HTTP_PRECONDITION, GURL())); 110 return CancelCallback(); 111 } 112 113 // Calls back the upload URL for subsequent ResumeUpload requests. 114 // InitiateUpload is an asynchronous function, so don't callback directly. 115 base::MessageLoop::current()->PostTask(FROM_HERE, 116 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadExistingFileURL))); 117 return CancelCallback(); 118 } 119 120 // Handles a request for uploading a chunk of bytes. 121 virtual CancelCallback ResumeUpload( 122 const GURL& upload_location, 123 int64 start_position, 124 int64 end_position, 125 int64 content_length, 126 const std::string& content_type, 127 const base::FilePath& local_file_path, 128 const UploadRangeCallback& callback, 129 const ProgressCallback& progress_callback) OVERRIDE { 130 // The upload range should start from the current first unreceived byte. 131 EXPECT_EQ(received_bytes_, start_position); 132 EXPECT_EQ(expected_upload_file_, local_file_path); 133 134 // The upload data must be split into 512KB chunks. 135 const int64 expected_chunk_end = 136 std::min(received_bytes_ + kUploadChunkSize, expected_content_length_); 137 EXPECT_EQ(expected_chunk_end, end_position); 138 139 // The upload URL returned by InitiateUpload() must be used. 140 EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location || 141 GURL(kTestUploadExistingFileURL) == upload_location); 142 143 // Other parameters should be the exact values passed to DriveUploader. 144 EXPECT_EQ(expected_content_length_, content_length); 145 EXPECT_EQ(kTestMimeType, content_type); 146 147 // Update the internal status of the current upload session. 148 resume_upload_call_count_++; 149 received_bytes_ = end_position; 150 151 // Callback progress 152 if (!progress_callback.is_null()) { 153 // For the testing purpose, it always notifies the progress at the end of 154 // each chunk uploading. 155 int64 chunk_size = end_position - start_position; 156 base::MessageLoop::current()->PostTask(FROM_HERE, 157 base::Bind(progress_callback, chunk_size, chunk_size)); 158 } 159 160 SendUploadRangeResponse(upload_location, callback); 161 return CancelCallback(); 162 } 163 164 // Handles a request to fetch the current upload status. 165 virtual CancelCallback GetUploadStatus( 166 const GURL& upload_location, 167 int64 content_length, 168 const UploadRangeCallback& callback) OVERRIDE { 169 EXPECT_EQ(expected_content_length_, content_length); 170 // The upload URL returned by InitiateUpload() must be used. 171 EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location || 172 GURL(kTestUploadExistingFileURL) == upload_location); 173 174 SendUploadRangeResponse(upload_location, callback); 175 return CancelCallback(); 176 } 177 178 // Runs |callback| with the current upload status. 179 void SendUploadRangeResponse(const GURL& upload_location, 180 const UploadRangeCallback& callback) { 181 // Callback with response. 182 UploadRangeResponse response; 183 scoped_ptr<FileResource> entry; 184 if (received_bytes_ == expected_content_length_) { 185 GDataErrorCode response_code = 186 upload_location == GURL(kTestUploadNewFileURL) ? 187 HTTP_CREATED : HTTP_SUCCESS; 188 response = UploadRangeResponse(response_code, -1, -1); 189 190 entry.reset(new FileResource); 191 entry->set_md5_checksum(kTestDummyMd5); 192 } else { 193 response = UploadRangeResponse( 194 HTTP_RESUME_INCOMPLETE, 0, received_bytes_); 195 } 196 // ResumeUpload is an asynchronous function, so don't callback directly. 197 base::MessageLoop::current()->PostTask(FROM_HERE, 198 base::Bind(callback, response, base::Passed(&entry))); 199 } 200 201 const base::FilePath expected_upload_file_; 202 const int64 expected_content_length_; 203 int64 received_bytes_; 204 int64 resume_upload_call_count_; 205}; 206 207// Mock DriveService that returns a failure at InitiateUpload(). 208class MockDriveServiceNoConnectionAtInitiate : public DummyDriveService { 209 // Returns error. 210 virtual CancelCallback InitiateUploadNewFile( 211 const std::string& content_type, 212 int64 content_length, 213 const std::string& parent_resource_id, 214 const std::string& title, 215 const InitiateUploadNewFileOptions& options, 216 const InitiateUploadCallback& callback) OVERRIDE { 217 base::MessageLoop::current()->PostTask(FROM_HERE, 218 base::Bind(callback, GDATA_NO_CONNECTION, GURL())); 219 return CancelCallback(); 220 } 221 222 virtual CancelCallback InitiateUploadExistingFile( 223 const std::string& content_type, 224 int64 content_length, 225 const std::string& resource_id, 226 const InitiateUploadExistingFileOptions& options, 227 const InitiateUploadCallback& callback) OVERRIDE { 228 base::MessageLoop::current()->PostTask(FROM_HERE, 229 base::Bind(callback, GDATA_NO_CONNECTION, GURL())); 230 return CancelCallback(); 231 } 232 233 // Should not be used. 234 virtual CancelCallback ResumeUpload( 235 const GURL& upload_url, 236 int64 start_position, 237 int64 end_position, 238 int64 content_length, 239 const std::string& content_type, 240 const base::FilePath& local_file_path, 241 const UploadRangeCallback& callback, 242 const ProgressCallback& progress_callback) OVERRIDE { 243 NOTREACHED(); 244 return CancelCallback(); 245 } 246}; 247 248// Mock DriveService that returns a failure at ResumeUpload(). 249class MockDriveServiceNoConnectionAtResume : public DummyDriveService { 250 // Succeeds and returns an upload location URL. 251 virtual CancelCallback InitiateUploadNewFile( 252 const std::string& content_type, 253 int64 content_length, 254 const std::string& parent_resource_id, 255 const std::string& title, 256 const InitiateUploadNewFileOptions& options, 257 const InitiateUploadCallback& callback) OVERRIDE { 258 base::MessageLoop::current()->PostTask(FROM_HERE, 259 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL))); 260 return CancelCallback(); 261 } 262 263 virtual CancelCallback InitiateUploadExistingFile( 264 const std::string& content_type, 265 int64 content_length, 266 const std::string& resource_id, 267 const InitiateUploadExistingFileOptions& options, 268 const InitiateUploadCallback& callback) OVERRIDE { 269 base::MessageLoop::current()->PostTask(FROM_HERE, 270 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadExistingFileURL))); 271 return CancelCallback(); 272 } 273 274 // Returns error. 275 virtual CancelCallback ResumeUpload( 276 const GURL& upload_url, 277 int64 start_position, 278 int64 end_position, 279 int64 content_length, 280 const std::string& content_type, 281 const base::FilePath& local_file_path, 282 const UploadRangeCallback& callback, 283 const ProgressCallback& progress_callback) OVERRIDE { 284 base::MessageLoop::current()->PostTask(FROM_HERE, 285 base::Bind(callback, 286 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1), 287 base::Passed(scoped_ptr<FileResource>()))); 288 return CancelCallback(); 289 } 290}; 291 292// Mock DriveService that returns a failure at GetUploadStatus(). 293class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService { 294 // Returns error. 295 virtual CancelCallback GetUploadStatus( 296 const GURL& upload_url, 297 int64 content_length, 298 const UploadRangeCallback& callback) OVERRIDE { 299 base::MessageLoop::current()->PostTask(FROM_HERE, 300 base::Bind(callback, 301 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1), 302 base::Passed(scoped_ptr<FileResource>()))); 303 return CancelCallback(); 304 } 305}; 306 307class DriveUploaderTest : public testing::Test { 308 public: 309 virtual void SetUp() OVERRIDE { 310 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 311 } 312 313 protected: 314 content::TestBrowserThreadBundle thread_bundle_; 315 base::ScopedTempDir temp_dir_; 316}; 317 318} // namespace 319 320TEST_F(DriveUploaderTest, UploadExisting0KB) { 321 base::FilePath local_path; 322 std::string data; 323 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 324 temp_dir_.path(), 0, &local_path, &data)); 325 326 GDataErrorCode error = GDATA_OTHER_ERROR; 327 GURL upload_location; 328 scoped_ptr<FileResource> entry; 329 330 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size()); 331 DriveUploader uploader(&mock_service, 332 base::MessageLoopProxy::current().get()); 333 std::vector<test_util::ProgressInfo> upload_progress_values; 334 uploader.UploadExistingFile( 335 kTestInitiateUploadResourceId, 336 local_path, 337 kTestMimeType, 338 DriveUploader::UploadExistingFileOptions(), 339 test_util::CreateCopyResultCallback( 340 &error, &upload_location, &entry), 341 base::Bind(&test_util::AppendProgressCallbackResult, 342 &upload_progress_values)); 343 base::RunLoop().RunUntilIdle(); 344 345 EXPECT_EQ(1, mock_service.resume_upload_call_count()); 346 EXPECT_EQ(0, mock_service.received_bytes()); 347 EXPECT_EQ(HTTP_SUCCESS, error); 348 EXPECT_TRUE(upload_location.is_empty()); 349 ASSERT_TRUE(entry); 350 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum()); 351 ASSERT_EQ(1U, upload_progress_values.size()); 352 EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values[0]); 353} 354 355TEST_F(DriveUploaderTest, UploadExisting512KB) { 356 base::FilePath local_path; 357 std::string data; 358 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 359 temp_dir_.path(), 512 * 1024, &local_path, &data)); 360 361 GDataErrorCode error = GDATA_OTHER_ERROR; 362 GURL upload_location; 363 scoped_ptr<FileResource> entry; 364 365 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size()); 366 DriveUploader uploader(&mock_service, 367 base::MessageLoopProxy::current().get()); 368 std::vector<test_util::ProgressInfo> upload_progress_values; 369 uploader.UploadExistingFile( 370 kTestInitiateUploadResourceId, 371 local_path, 372 kTestMimeType, 373 DriveUploader::UploadExistingFileOptions(), 374 test_util::CreateCopyResultCallback( 375 &error, &upload_location, &entry), 376 base::Bind(&test_util::AppendProgressCallbackResult, 377 &upload_progress_values)); 378 base::RunLoop().RunUntilIdle(); 379 380 // 512KB upload should not be split into multiple chunks. 381 EXPECT_EQ(1, mock_service.resume_upload_call_count()); 382 EXPECT_EQ(512 * 1024, mock_service.received_bytes()); 383 EXPECT_EQ(HTTP_SUCCESS, error); 384 EXPECT_TRUE(upload_location.is_empty()); 385 ASSERT_TRUE(entry); 386 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum()); 387 ASSERT_EQ(1U, upload_progress_values.size()); 388 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024), 389 upload_progress_values[0]); 390} 391 392TEST_F(DriveUploaderTest, InitiateUploadFail) { 393 base::FilePath local_path; 394 std::string data; 395 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 396 temp_dir_.path(), 512 * 1024, &local_path, &data)); 397 398 GDataErrorCode error = HTTP_SUCCESS; 399 GURL upload_location; 400 scoped_ptr<FileResource> entry; 401 402 MockDriveServiceNoConnectionAtInitiate mock_service; 403 DriveUploader uploader(&mock_service, 404 base::MessageLoopProxy::current().get()); 405 uploader.UploadExistingFile(kTestInitiateUploadResourceId, 406 local_path, 407 kTestMimeType, 408 DriveUploader::UploadExistingFileOptions(), 409 test_util::CreateCopyResultCallback( 410 &error, &upload_location, &entry), 411 google_apis::ProgressCallback()); 412 base::RunLoop().RunUntilIdle(); 413 414 EXPECT_EQ(GDATA_NO_CONNECTION, error); 415 EXPECT_TRUE(upload_location.is_empty()); 416 EXPECT_FALSE(entry); 417} 418 419TEST_F(DriveUploaderTest, InitiateUploadNoConflict) { 420 base::FilePath local_path; 421 std::string data; 422 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 423 temp_dir_.path(), 512 * 1024, &local_path, &data)); 424 425 GDataErrorCode error = GDATA_OTHER_ERROR; 426 GURL upload_location; 427 scoped_ptr<FileResource> entry; 428 429 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size()); 430 DriveUploader uploader(&mock_service, 431 base::MessageLoopProxy::current().get()); 432 DriveUploader::UploadExistingFileOptions options; 433 options.etag = kTestETag; 434 uploader.UploadExistingFile(kTestInitiateUploadResourceId, 435 local_path, 436 kTestMimeType, 437 options, 438 test_util::CreateCopyResultCallback( 439 &error, &upload_location, &entry), 440 google_apis::ProgressCallback()); 441 base::RunLoop().RunUntilIdle(); 442 443 EXPECT_EQ(HTTP_SUCCESS, error); 444 EXPECT_TRUE(upload_location.is_empty()); 445} 446 447TEST_F(DriveUploaderTest, InitiateUploadConflict) { 448 base::FilePath local_path; 449 std::string data; 450 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 451 temp_dir_.path(), 512 * 1024, &local_path, &data)); 452 const std::string kDestinationETag("destination_etag"); 453 454 GDataErrorCode error = GDATA_OTHER_ERROR; 455 GURL upload_location; 456 scoped_ptr<FileResource> entry; 457 458 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size()); 459 DriveUploader uploader(&mock_service, 460 base::MessageLoopProxy::current().get()); 461 DriveUploader::UploadExistingFileOptions options; 462 options.etag = kDestinationETag; 463 uploader.UploadExistingFile(kTestInitiateUploadResourceId, 464 local_path, 465 kTestMimeType, 466 options, 467 test_util::CreateCopyResultCallback( 468 &error, &upload_location, &entry), 469 google_apis::ProgressCallback()); 470 base::RunLoop().RunUntilIdle(); 471 472 EXPECT_EQ(HTTP_CONFLICT, error); 473 EXPECT_TRUE(upload_location.is_empty()); 474} 475 476TEST_F(DriveUploaderTest, ResumeUploadFail) { 477 base::FilePath local_path; 478 std::string data; 479 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 480 temp_dir_.path(), 512 * 1024, &local_path, &data)); 481 482 GDataErrorCode error = HTTP_SUCCESS; 483 GURL upload_location; 484 scoped_ptr<FileResource> entry; 485 486 MockDriveServiceNoConnectionAtResume mock_service; 487 DriveUploader uploader(&mock_service, 488 base::MessageLoopProxy::current().get()); 489 uploader.UploadExistingFile(kTestInitiateUploadResourceId, 490 local_path, 491 kTestMimeType, 492 DriveUploader::UploadExistingFileOptions(), 493 test_util::CreateCopyResultCallback( 494 &error, &upload_location, &entry), 495 google_apis::ProgressCallback()); 496 base::RunLoop().RunUntilIdle(); 497 498 EXPECT_EQ(GDATA_NO_CONNECTION, error); 499 EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location); 500} 501 502TEST_F(DriveUploaderTest, GetUploadStatusFail) { 503 base::FilePath local_path; 504 std::string data; 505 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 506 temp_dir_.path(), 512 * 1024, &local_path, &data)); 507 508 GDataErrorCode error = HTTP_SUCCESS; 509 GURL upload_location; 510 scoped_ptr<FileResource> entry; 511 512 MockDriveServiceNoConnectionAtGetUploadStatus mock_service; 513 DriveUploader uploader(&mock_service, 514 base::MessageLoopProxy::current().get()); 515 uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL), 516 local_path, 517 kTestMimeType, 518 test_util::CreateCopyResultCallback( 519 &error, &upload_location, &entry), 520 google_apis::ProgressCallback()); 521 base::RunLoop().RunUntilIdle(); 522 523 EXPECT_EQ(GDATA_NO_CONNECTION, error); 524 EXPECT_TRUE(upload_location.is_empty()); 525} 526 527TEST_F(DriveUploaderTest, NonExistingSourceFile) { 528 GDataErrorCode error = GDATA_OTHER_ERROR; 529 GURL upload_location; 530 scoped_ptr<FileResource> entry; 531 532 DriveUploader uploader(NULL, // NULL, the service won't be used. 533 base::MessageLoopProxy::current().get()); 534 uploader.UploadExistingFile( 535 kTestInitiateUploadResourceId, 536 temp_dir_.path().AppendASCII("_this_path_should_not_exist_"), 537 kTestMimeType, 538 DriveUploader::UploadExistingFileOptions(), 539 test_util::CreateCopyResultCallback( 540 &error, &upload_location, &entry), 541 google_apis::ProgressCallback()); 542 base::RunLoop().RunUntilIdle(); 543 544 // Should return failure without doing any attempt to connect to the server. 545 EXPECT_EQ(HTTP_NOT_FOUND, error); 546 EXPECT_TRUE(upload_location.is_empty()); 547} 548 549TEST_F(DriveUploaderTest, ResumeUpload) { 550 base::FilePath local_path; 551 std::string data; 552 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize( 553 temp_dir_.path(), 1024 * 1024, &local_path, &data)); 554 555 GDataErrorCode error = GDATA_OTHER_ERROR; 556 GURL upload_location; 557 scoped_ptr<FileResource> entry; 558 559 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size()); 560 DriveUploader uploader(&mock_service, 561 base::MessageLoopProxy::current().get()); 562 // Emulate the situation that the only first part is successfully uploaded, 563 // but not the latter half. 564 mock_service.set_received_bytes(512 * 1024); 565 566 std::vector<test_util::ProgressInfo> upload_progress_values; 567 uploader.ResumeUploadFile( 568 GURL(kTestUploadExistingFileURL), 569 local_path, 570 kTestMimeType, 571 test_util::CreateCopyResultCallback( 572 &error, &upload_location, &entry), 573 base::Bind(&test_util::AppendProgressCallbackResult, 574 &upload_progress_values)); 575 base::RunLoop().RunUntilIdle(); 576 577 EXPECT_EQ(1, mock_service.resume_upload_call_count()); 578 EXPECT_EQ(1024 * 1024, mock_service.received_bytes()); 579 EXPECT_EQ(HTTP_SUCCESS, error); 580 EXPECT_TRUE(upload_location.is_empty()); 581 ASSERT_TRUE(entry); 582 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum()); 583 ASSERT_EQ(1U, upload_progress_values.size()); 584 EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024), 585 upload_progress_values[0]); 586} 587 588} // namespace drive 589