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 "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"
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;
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";
53 // Mock DriveService that verifies if the uploaded content matches the preset
55 class MockDriveServiceWithUploadExpectation : public DummyDriveService {
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),
65 resume_upload_call_count_(0) {}
67 int64 received_bytes() const { return received_bytes_; }
68 void set_received_bytes(int64 received_bytes) {
69 received_bytes_ = received_bytes;
72 int64 resume_upload_call_count() const { return resume_upload_call_count_; }
75 // DriveServiceInterface overrides.
76 // Handles a request for obtaining an upload location URL.
77 virtual CancelCallback InitiateUploadNewFile(
78 const std::string& content_type,
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);
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();
95 virtual CancelCallback InitiateUploadExistingFile(
96 const std::string& content_type,
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);
105 if (!etag.empty() && etag != kTestETag) {
106 base::MessageLoop::current()->PostTask(FROM_HERE,
107 base::Bind(callback, HTTP_PRECONDITION, GURL()));
108 return CancelCallback();
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();
118 // Handles a request for uploading a chunk of bytes.
119 virtual CancelCallback ResumeUpload(
120 const GURL& upload_location,
121 int64 start_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);
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);
137 // The upload URL returned by InitiateUpload() must be used.
138 EXPECT_TRUE(GURL(kTestUploadNewFileURL) == upload_location ||
139 GURL(kTestUploadExistingFileURL) == upload_location);
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);
145 // Update the internal status of the current upload session.
146 resume_upload_call_count_++;
147 received_bytes_ = end_position;
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));
158 SendUploadRangeResponse(upload_location, callback);
159 return CancelCallback();
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);
172 SendUploadRangeResponse(upload_location, callback);
173 return CancelCallback();
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);
188 base::DictionaryValue dict;
189 dict.Set("id.$t", new base::StringValue(kTestDummyId));
190 entry = ResourceEntry::CreateFrom(dict);
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 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();
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();
231 // Should not be used.
232 virtual CancelCallback ResumeUpload(
233 const GURL& upload_url,
234 int64 start_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 {
242 return CancelCallback();
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();
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();
272 virtual CancelCallback ResumeUpload(
273 const GURL& upload_url,
274 int64 start_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,
283 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1),
284 base::Passed(scoped_ptr<ResourceEntry>())));
285 return CancelCallback();
289 // Mock DriveService that returns a failure at GetUploadStatus().
290 class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService {
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,
298 UploadRangeResponse(GDATA_NO_CONNECTION, -1, -1),
299 base::Passed(scoped_ptr<ResourceEntry>())));
300 return CancelCallback();
304 class DriveUploaderTest : public testing::Test {
306 virtual void SetUp() OVERRIDE {
307 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
311 content::TestBrowserThreadBundle thread_bundle_;
312 base::ScopedTempDir temp_dir_;
317 TEST_F(DriveUploaderTest, UploadExisting0KB) {
318 base::FilePath local_path;
320 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
321 temp_dir_.path(), 0, &local_path, &data));
323 GDataErrorCode error = GDATA_OTHER_ERROR;
324 GURL upload_location;
325 scoped_ptr<ResourceEntry> resource_entry;
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,
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();
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]);
352 TEST_F(DriveUploaderTest, UploadExisting512KB) {
353 base::FilePath local_path;
355 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
356 temp_dir_.path(), 512 * 1024, &local_path, &data));
358 GDataErrorCode error = GDATA_OTHER_ERROR;
359 GURL upload_location;
360 scoped_ptr<ResourceEntry> resource_entry;
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,
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();
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]);
389 TEST_F(DriveUploaderTest, InitiateUploadFail) {
390 base::FilePath local_path;
392 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
393 temp_dir_.path(), 512 * 1024, &local_path, &data));
395 GDataErrorCode error = HTTP_SUCCESS;
396 GURL upload_location;
397 scoped_ptr<ResourceEntry> resource_entry;
399 MockDriveServiceNoConnectionAtInitiate mock_service;
400 DriveUploader uploader(&mock_service,
401 base::MessageLoopProxy::current().get());
402 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
405 std::string(), // etag
406 test_util::CreateCopyResultCallback(
407 &error, &upload_location, &resource_entry),
408 google_apis::ProgressCallback());
409 base::RunLoop().RunUntilIdle();
411 EXPECT_EQ(GDATA_NO_CONNECTION, error);
412 EXPECT_TRUE(upload_location.is_empty());
413 EXPECT_FALSE(resource_entry);
416 TEST_F(DriveUploaderTest, InitiateUploadNoConflict) {
417 base::FilePath local_path;
419 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
420 temp_dir_.path(), 512 * 1024, &local_path, &data));
422 GDataErrorCode error = GDATA_OTHER_ERROR;
423 GURL upload_location;
424 scoped_ptr<ResourceEntry> resource_entry;
426 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
427 DriveUploader uploader(&mock_service,
428 base::MessageLoopProxy::current().get());
429 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
433 test_util::CreateCopyResultCallback(
434 &error, &upload_location, &resource_entry),
435 google_apis::ProgressCallback());
436 base::RunLoop().RunUntilIdle();
438 EXPECT_EQ(HTTP_SUCCESS, error);
439 EXPECT_TRUE(upload_location.is_empty());
442 TEST_F(DriveUploaderTest, InitiateUploadConflict) {
443 base::FilePath local_path;
445 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
446 temp_dir_.path(), 512 * 1024, &local_path, &data));
447 const std::string kDestinationETag("destination_etag");
449 GDataErrorCode error = GDATA_OTHER_ERROR;
450 GURL upload_location;
451 scoped_ptr<ResourceEntry> resource_entry;
453 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
454 DriveUploader uploader(&mock_service,
455 base::MessageLoopProxy::current().get());
456 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
460 test_util::CreateCopyResultCallback(
461 &error, &upload_location, &resource_entry),
462 google_apis::ProgressCallback());
463 base::RunLoop().RunUntilIdle();
465 EXPECT_EQ(HTTP_CONFLICT, error);
466 EXPECT_TRUE(upload_location.is_empty());
469 TEST_F(DriveUploaderTest, ResumeUploadFail) {
470 base::FilePath local_path;
472 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
473 temp_dir_.path(), 512 * 1024, &local_path, &data));
475 GDataErrorCode error = HTTP_SUCCESS;
476 GURL upload_location;
477 scoped_ptr<ResourceEntry> resource_entry;
479 MockDriveServiceNoConnectionAtResume mock_service;
480 DriveUploader uploader(&mock_service,
481 base::MessageLoopProxy::current().get());
482 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
485 std::string(), // etag
486 test_util::CreateCopyResultCallback(
487 &error, &upload_location, &resource_entry),
488 google_apis::ProgressCallback());
489 base::RunLoop().RunUntilIdle();
491 EXPECT_EQ(GDATA_NO_CONNECTION, error);
492 EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location);
495 TEST_F(DriveUploaderTest, GetUploadStatusFail) {
496 base::FilePath local_path;
498 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
499 temp_dir_.path(), 512 * 1024, &local_path, &data));
501 GDataErrorCode error = HTTP_SUCCESS;
502 GURL upload_location;
503 scoped_ptr<ResourceEntry> resource_entry;
505 MockDriveServiceNoConnectionAtGetUploadStatus mock_service;
506 DriveUploader uploader(&mock_service,
507 base::MessageLoopProxy::current().get());
508 uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL),
511 test_util::CreateCopyResultCallback(
512 &error, &upload_location, &resource_entry),
513 google_apis::ProgressCallback());
514 base::RunLoop().RunUntilIdle();
516 EXPECT_EQ(GDATA_NO_CONNECTION, error);
517 EXPECT_TRUE(upload_location.is_empty());
520 TEST_F(DriveUploaderTest, NonExistingSourceFile) {
521 GDataErrorCode error = GDATA_OTHER_ERROR;
522 GURL upload_location;
523 scoped_ptr<ResourceEntry> resource_entry;
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_"),
531 std::string(), // etag
532 test_util::CreateCopyResultCallback(
533 &error, &upload_location, &resource_entry),
534 google_apis::ProgressCallback());
535 base::RunLoop().RunUntilIdle();
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());
542 TEST_F(DriveUploaderTest, ResumeUpload) {
543 base::FilePath local_path;
545 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
546 temp_dir_.path(), 1024 * 1024, &local_path, &data));
548 GDataErrorCode error = GDATA_OTHER_ERROR;
549 GURL upload_location;
550 scoped_ptr<ResourceEntry> resource_entry;
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);
559 std::vector<test_util::ProgressInfo> upload_progress_values;
560 uploader.ResumeUploadFile(
561 GURL(kTestUploadExistingFileURL),
564 test_util::CreateCopyResultCallback(
565 &error, &upload_location, &resource_entry),
566 base::Bind(&test_util::AppendProgressCallbackResult,
567 &upload_progress_values));
568 base::RunLoop().RunUntilIdle();
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]);