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