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.
5 #include "chrome/browser/drive/drive_uploader.h"
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"
22 using google_apis::CancelCallback;
23 using google_apis::FileResource;
24 using google_apis::GDataErrorCode;
25 using google_apis::GDATA_NO_CONNECTION;
26 using google_apis::GDATA_OTHER_ERROR;
27 using google_apis::HTTP_CONFLICT;
28 using google_apis::HTTP_CREATED;
29 using google_apis::HTTP_NOT_FOUND;
30 using google_apis::HTTP_PRECONDITION;
31 using google_apis::HTTP_RESUME_INCOMPLETE;
32 using google_apis::HTTP_SUCCESS;
33 using google_apis::InitiateUploadCallback;
34 using google_apis::ProgressCallback;
35 using google_apis::UploadRangeResponse;
36 using google_apis::drive::UploadRangeCallback;
37 namespace test_util = google_apis::test_util;
43 const char kTestDummyMd5[] = "dummy_md5";
44 const char kTestDocumentTitle[] = "Hello world";
45 const char kTestInitiateUploadParentResourceId[] = "parent_resource_id";
46 const char kTestInitiateUploadResourceId[] = "resource_id";
47 const char kTestMimeType[] = "text/plain";
48 const char kTestUploadNewFileURL[] = "http://test/upload_location/new_file";
49 const char kTestUploadExistingFileURL[] =
50 "http://test/upload_location/existing_file";
51 const int64 kUploadChunkSize = 512 * 1024;
52 const char kTestETag[] = "test_etag";
54 // Mock DriveService that verifies if the uploaded content matches the preset
56 class MockDriveServiceWithUploadExpectation : public DummyDriveService {
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),
66 resume_upload_call_count_(0) {}
68 int64 received_bytes() const { return received_bytes_; }
69 void set_received_bytes(int64 received_bytes) {
70 received_bytes_ = received_bytes;
73 int64 resume_upload_call_count() const { return resume_upload_call_count_; }
76 // DriveServiceInterface overrides.
77 // Handles a request for obtaining an upload location URL.
78 CancelCallback InitiateUploadNewFile(
79 const std::string& content_type,
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);
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();
97 CancelCallback InitiateUploadExistingFile(
98 const std::string& content_type,
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);
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();
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();
120 // Handles a request for uploading a chunk of bytes.
121 CancelCallback ResumeUpload(
122 const GURL& upload_location,
123 int64 start_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);
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);
139 // The upload URL returned by InitiateUpload() must be used.
140 EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location ||
141 GURL(kTestUploadExistingFileURL) == upload_location);
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);
147 // Update the internal status of the current upload session.
148 resume_upload_call_count_++;
149 received_bytes_ = end_position;
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));
160 SendUploadRangeResponse(upload_location, callback);
161 return CancelCallback();
164 // Handles a request to fetch the current upload status.
165 CancelCallback GetUploadStatus(const GURL& upload_location,
166 int64 content_length,
167 const UploadRangeCallback& callback) override {
168 EXPECT_EQ(expected_content_length_, content_length);
169 // The upload URL returned by InitiateUpload() must be used.
170 EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location ||
171 GURL(kTestUploadExistingFileURL) == upload_location);
173 SendUploadRangeResponse(upload_location, callback);
174 return CancelCallback();
177 // Runs |callback| with the current upload status.
178 void SendUploadRangeResponse(const GURL& upload_location,
179 const UploadRangeCallback& callback) {
180 // Callback with response.
181 UploadRangeResponse response;
182 scoped_ptr<FileResource> entry;
183 if (received_bytes_ == expected_content_length_) {
184 GDataErrorCode response_code =
185 upload_location == GURL(kTestUploadNewFileURL) ?
186 HTTP_CREATED : HTTP_SUCCESS;
187 response = UploadRangeResponse(response_code, -1, -1);
189 entry.reset(new FileResource);
190 entry->set_md5_checksum(kTestDummyMd5);
192 response = UploadRangeResponse(
193 HTTP_RESUME_INCOMPLETE, 0, received_bytes_);
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)));
200 const base::FilePath expected_upload_file_;
201 const int64 expected_content_length_;
202 int64 received_bytes_;
203 int64 resume_upload_call_count_;
206 // Mock DriveService that returns a failure at InitiateUpload().
207 class MockDriveServiceNoConnectionAtInitiate : public DummyDriveService {
209 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 InitiateUploadNewFileOptions& options,
215 const InitiateUploadCallback& callback) override {
216 base::MessageLoop::current()->PostTask(FROM_HERE,
217 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
218 return CancelCallback();
221 CancelCallback InitiateUploadExistingFile(
222 const std::string& content_type,
223 int64 content_length,
224 const std::string& resource_id,
225 const InitiateUploadExistingFileOptions& options,
226 const InitiateUploadCallback& callback) override {
227 base::MessageLoop::current()->PostTask(FROM_HERE,
228 base::Bind(callback, GDATA_NO_CONNECTION, GURL()));
229 return CancelCallback();
232 // Should not be used.
233 CancelCallback ResumeUpload(
234 const GURL& upload_url,
235 int64 start_position,
237 int64 content_length,
238 const std::string& content_type,
239 const base::FilePath& local_file_path,
240 const UploadRangeCallback& callback,
241 const ProgressCallback& progress_callback) override {
243 return CancelCallback();
247 // Mock DriveService that returns a failure at ResumeUpload().
248 class MockDriveServiceNoConnectionAtResume : public DummyDriveService {
249 // Succeeds and returns an upload location URL.
250 CancelCallback InitiateUploadNewFile(
251 const std::string& content_type,
252 int64 content_length,
253 const std::string& parent_resource_id,
254 const std::string& title,
255 const InitiateUploadNewFileOptions& options,
256 const InitiateUploadCallback& callback) override {
257 base::MessageLoop::current()->PostTask(FROM_HERE,
258 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
259 return CancelCallback();
262 CancelCallback InitiateUploadExistingFile(
263 const std::string& content_type,
264 int64 content_length,
265 const std::string& resource_id,
266 const InitiateUploadExistingFileOptions& options,
267 const InitiateUploadCallback& callback) override {
268 base::MessageLoop::current()->PostTask(FROM_HERE,
269 base::Bind(callback, HTTP_SUCCESS, GURL(kTestUploadExistingFileURL)));
270 return CancelCallback();
274 CancelCallback ResumeUpload(
275 const GURL& upload_url,
276 int64 start_position,
278 int64 content_length,
279 const std::string& content_type,
280 const base::FilePath& local_file_path,
281 const UploadRangeCallback& callback,
282 const ProgressCallback& progress_callback) override {
283 base::MessageLoop::current()->PostTask(FROM_HERE,
285 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1),
286 base::Passed(scoped_ptr<FileResource>())));
287 return CancelCallback();
291 // Mock DriveService that returns a failure at GetUploadStatus().
292 class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService {
294 CancelCallback GetUploadStatus(const GURL& upload_url,
295 int64 content_length,
296 const UploadRangeCallback& callback) override {
297 base::MessageLoop::current()->PostTask(FROM_HERE,
299 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1),
300 base::Passed(scoped_ptr<FileResource>())));
301 return CancelCallback();
305 class DriveUploaderTest : public testing::Test {
307 void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
310 content::TestBrowserThreadBundle thread_bundle_;
311 base::ScopedTempDir temp_dir_;
316 TEST_F(DriveUploaderTest, UploadExisting0KB) {
317 base::FilePath local_path;
319 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
320 temp_dir_.path(), 0, &local_path, &data));
322 GDataErrorCode error = GDATA_OTHER_ERROR;
323 GURL upload_location;
324 scoped_ptr<FileResource> entry;
326 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
327 DriveUploader uploader(&mock_service,
328 base::MessageLoopProxy::current().get());
329 std::vector<test_util::ProgressInfo> upload_progress_values;
330 uploader.UploadExistingFile(
331 kTestInitiateUploadResourceId,
334 DriveUploader::UploadExistingFileOptions(),
335 test_util::CreateCopyResultCallback(
336 &error, &upload_location, &entry),
337 base::Bind(&test_util::AppendProgressCallbackResult,
338 &upload_progress_values));
339 base::RunLoop().RunUntilIdle();
341 EXPECT_EQ(1, mock_service.resume_upload_call_count());
342 EXPECT_EQ(0, mock_service.received_bytes());
343 EXPECT_EQ(HTTP_SUCCESS, error);
344 EXPECT_TRUE(upload_location.is_empty());
346 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
347 ASSERT_EQ(1U, upload_progress_values.size());
348 EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values[0]);
351 TEST_F(DriveUploaderTest, UploadExisting512KB) {
352 base::FilePath local_path;
354 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
355 temp_dir_.path(), 512 * 1024, &local_path, &data));
357 GDataErrorCode error = GDATA_OTHER_ERROR;
358 GURL upload_location;
359 scoped_ptr<FileResource> entry;
361 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
362 DriveUploader uploader(&mock_service,
363 base::MessageLoopProxy::current().get());
364 std::vector<test_util::ProgressInfo> upload_progress_values;
365 uploader.UploadExistingFile(
366 kTestInitiateUploadResourceId,
369 DriveUploader::UploadExistingFileOptions(),
370 test_util::CreateCopyResultCallback(
371 &error, &upload_location, &entry),
372 base::Bind(&test_util::AppendProgressCallbackResult,
373 &upload_progress_values));
374 base::RunLoop().RunUntilIdle();
376 // 512KB upload should not be split into multiple chunks.
377 EXPECT_EQ(1, mock_service.resume_upload_call_count());
378 EXPECT_EQ(512 * 1024, mock_service.received_bytes());
379 EXPECT_EQ(HTTP_SUCCESS, error);
380 EXPECT_TRUE(upload_location.is_empty());
382 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
383 ASSERT_EQ(1U, upload_progress_values.size());
384 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
385 upload_progress_values[0]);
388 TEST_F(DriveUploaderTest, InitiateUploadFail) {
389 base::FilePath local_path;
391 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
392 temp_dir_.path(), 512 * 1024, &local_path, &data));
394 GDataErrorCode error = HTTP_SUCCESS;
395 GURL upload_location;
396 scoped_ptr<FileResource> entry;
398 MockDriveServiceNoConnectionAtInitiate mock_service;
399 DriveUploader uploader(&mock_service,
400 base::MessageLoopProxy::current().get());
401 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
404 DriveUploader::UploadExistingFileOptions(),
405 test_util::CreateCopyResultCallback(
406 &error, &upload_location, &entry),
407 google_apis::ProgressCallback());
408 base::RunLoop().RunUntilIdle();
410 EXPECT_EQ(GDATA_NO_CONNECTION, error);
411 EXPECT_TRUE(upload_location.is_empty());
415 TEST_F(DriveUploaderTest, InitiateUploadNoConflict) {
416 base::FilePath local_path;
418 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
419 temp_dir_.path(), 512 * 1024, &local_path, &data));
421 GDataErrorCode error = GDATA_OTHER_ERROR;
422 GURL upload_location;
423 scoped_ptr<FileResource> entry;
425 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
426 DriveUploader uploader(&mock_service,
427 base::MessageLoopProxy::current().get());
428 DriveUploader::UploadExistingFileOptions options;
429 options.etag = kTestETag;
430 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
434 test_util::CreateCopyResultCallback(
435 &error, &upload_location, &entry),
436 google_apis::ProgressCallback());
437 base::RunLoop().RunUntilIdle();
439 EXPECT_EQ(HTTP_SUCCESS, error);
440 EXPECT_TRUE(upload_location.is_empty());
443 TEST_F(DriveUploaderTest, InitiateUploadConflict) {
444 base::FilePath local_path;
446 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
447 temp_dir_.path(), 512 * 1024, &local_path, &data));
448 const std::string kDestinationETag("destination_etag");
450 GDataErrorCode error = GDATA_OTHER_ERROR;
451 GURL upload_location;
452 scoped_ptr<FileResource> entry;
454 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
455 DriveUploader uploader(&mock_service,
456 base::MessageLoopProxy::current().get());
457 DriveUploader::UploadExistingFileOptions options;
458 options.etag = kDestinationETag;
459 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
463 test_util::CreateCopyResultCallback(
464 &error, &upload_location, &entry),
465 google_apis::ProgressCallback());
466 base::RunLoop().RunUntilIdle();
468 EXPECT_EQ(HTTP_CONFLICT, error);
469 EXPECT_TRUE(upload_location.is_empty());
472 TEST_F(DriveUploaderTest, ResumeUploadFail) {
473 base::FilePath local_path;
475 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
476 temp_dir_.path(), 512 * 1024, &local_path, &data));
478 GDataErrorCode error = HTTP_SUCCESS;
479 GURL upload_location;
480 scoped_ptr<FileResource> entry;
482 MockDriveServiceNoConnectionAtResume mock_service;
483 DriveUploader uploader(&mock_service,
484 base::MessageLoopProxy::current().get());
485 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
488 DriveUploader::UploadExistingFileOptions(),
489 test_util::CreateCopyResultCallback(
490 &error, &upload_location, &entry),
491 google_apis::ProgressCallback());
492 base::RunLoop().RunUntilIdle();
494 EXPECT_EQ(GDATA_NO_CONNECTION, error);
495 EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location);
498 TEST_F(DriveUploaderTest, GetUploadStatusFail) {
499 base::FilePath local_path;
501 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
502 temp_dir_.path(), 512 * 1024, &local_path, &data));
504 GDataErrorCode error = HTTP_SUCCESS;
505 GURL upload_location;
506 scoped_ptr<FileResource> entry;
508 MockDriveServiceNoConnectionAtGetUploadStatus mock_service;
509 DriveUploader uploader(&mock_service,
510 base::MessageLoopProxy::current().get());
511 uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL),
514 test_util::CreateCopyResultCallback(
515 &error, &upload_location, &entry),
516 google_apis::ProgressCallback());
517 base::RunLoop().RunUntilIdle();
519 EXPECT_EQ(GDATA_NO_CONNECTION, error);
520 EXPECT_TRUE(upload_location.is_empty());
523 TEST_F(DriveUploaderTest, NonExistingSourceFile) {
524 GDataErrorCode error = GDATA_OTHER_ERROR;
525 GURL upload_location;
526 scoped_ptr<FileResource> entry;
528 DriveUploader uploader(NULL, // NULL, the service won't be used.
529 base::MessageLoopProxy::current().get());
530 uploader.UploadExistingFile(
531 kTestInitiateUploadResourceId,
532 temp_dir_.path().AppendASCII("_this_path_should_not_exist_"),
534 DriveUploader::UploadExistingFileOptions(),
535 test_util::CreateCopyResultCallback(
536 &error, &upload_location, &entry),
537 google_apis::ProgressCallback());
538 base::RunLoop().RunUntilIdle();
540 // Should return failure without doing any attempt to connect to the server.
541 EXPECT_EQ(HTTP_NOT_FOUND, error);
542 EXPECT_TRUE(upload_location.is_empty());
545 TEST_F(DriveUploaderTest, ResumeUpload) {
546 base::FilePath local_path;
548 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
549 temp_dir_.path(), 1024 * 1024, &local_path, &data));
551 GDataErrorCode error = GDATA_OTHER_ERROR;
552 GURL upload_location;
553 scoped_ptr<FileResource> entry;
555 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
556 DriveUploader uploader(&mock_service,
557 base::MessageLoopProxy::current().get());
558 // Emulate the situation that the only first part is successfully uploaded,
559 // but not the latter half.
560 mock_service.set_received_bytes(512 * 1024);
562 std::vector<test_util::ProgressInfo> upload_progress_values;
563 uploader.ResumeUploadFile(
564 GURL(kTestUploadExistingFileURL),
567 test_util::CreateCopyResultCallback(
568 &error, &upload_location, &entry),
569 base::Bind(&test_util::AppendProgressCallbackResult,
570 &upload_progress_values));
571 base::RunLoop().RunUntilIdle();
573 EXPECT_EQ(1, mock_service.resume_upload_call_count());
574 EXPECT_EQ(1024 * 1024, mock_service.received_bytes());
575 EXPECT_EQ(HTTP_SUCCESS, error);
576 EXPECT_TRUE(upload_location.is_empty());
578 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
579 ASSERT_EQ(1U, upload_progress_values.size());
580 EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024),
581 upload_progress_values[0]);