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/test_util.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 InitiateUploadNewFileOptions
& options
,
83 const InitiateUploadCallback
& callback
) OVERRIDE
{
84 EXPECT_EQ(kTestDocumentTitle
, title
);
85 EXPECT_EQ(kTestMimeType
, content_type
);
86 EXPECT_EQ(expected_content_length_
, content_length
);
87 EXPECT_EQ(kTestInitiateUploadParentResourceId
, parent_resource_id
);
89 // Calls back the upload URL for subsequent ResumeUpload requests.
90 // InitiateUpload is an asynchronous function, so don't callback directly.
91 base::MessageLoop::current()->PostTask(FROM_HERE
,
92 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadNewFileURL
)));
93 return CancelCallback();
96 virtual CancelCallback
InitiateUploadExistingFile(
97 const std::string
& content_type
,
99 const std::string
& resource_id
,
100 const InitiateUploadExistingFileOptions
& options
,
101 const InitiateUploadCallback
& callback
) OVERRIDE
{
102 EXPECT_EQ(kTestMimeType
, content_type
);
103 EXPECT_EQ(expected_content_length_
, content_length
);
104 EXPECT_EQ(kTestInitiateUploadResourceId
, resource_id
);
106 if (!options
.etag
.empty() && options
.etag
!= kTestETag
) {
107 base::MessageLoop::current()->PostTask(FROM_HERE
,
108 base::Bind(callback
, HTTP_PRECONDITION
, GURL()));
109 return CancelCallback();
112 // Calls back the upload URL for subsequent ResumeUpload requests.
113 // InitiateUpload is an asynchronous function, so don't callback directly.
114 base::MessageLoop::current()->PostTask(FROM_HERE
,
115 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadExistingFileURL
)));
116 return CancelCallback();
119 // Handles a request for uploading a chunk of bytes.
120 virtual CancelCallback
ResumeUpload(
121 const GURL
& upload_location
,
122 int64 start_position
,
124 int64 content_length
,
125 const std::string
& content_type
,
126 const base::FilePath
& local_file_path
,
127 const UploadRangeCallback
& callback
,
128 const ProgressCallback
& progress_callback
) OVERRIDE
{
129 // The upload range should start from the current first unreceived byte.
130 EXPECT_EQ(received_bytes_
, start_position
);
131 EXPECT_EQ(expected_upload_file_
, local_file_path
);
133 // The upload data must be split into 512KB chunks.
134 const int64 expected_chunk_end
=
135 std::min(received_bytes_
+ kUploadChunkSize
, expected_content_length_
);
136 EXPECT_EQ(expected_chunk_end
, end_position
);
138 // The upload URL returned by InitiateUpload() must be used.
139 EXPECT_TRUE(GURL(kTestUploadNewFileURL
) == upload_location
||
140 GURL(kTestUploadExistingFileURL
) == upload_location
);
142 // Other parameters should be the exact values passed to DriveUploader.
143 EXPECT_EQ(expected_content_length_
, content_length
);
144 EXPECT_EQ(kTestMimeType
, content_type
);
146 // Update the internal status of the current upload session.
147 resume_upload_call_count_
++;
148 received_bytes_
= end_position
;
151 if (!progress_callback
.is_null()) {
152 // For the testing purpose, it always notifies the progress at the end of
153 // each chunk uploading.
154 int64 chunk_size
= end_position
- start_position
;
155 base::MessageLoop::current()->PostTask(FROM_HERE
,
156 base::Bind(progress_callback
, chunk_size
, chunk_size
));
159 SendUploadRangeResponse(upload_location
, callback
);
160 return CancelCallback();
163 // Handles a request to fetch the current upload status.
164 virtual CancelCallback
GetUploadStatus(
165 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
<ResourceEntry
> 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 base::DictionaryValue dict
;
190 dict
.Set("id.$t", new base::StringValue(kTestDummyId
));
191 entry
= ResourceEntry::CreateFrom(dict
);
193 response
= UploadRangeResponse(
194 HTTP_RESUME_INCOMPLETE
, 0, received_bytes_
);
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
)));
201 const base::FilePath expected_upload_file_
;
202 const int64 expected_content_length_
;
203 int64 received_bytes_
;
204 int64 resume_upload_call_count_
;
207 // Mock DriveService that returns a failure at InitiateUpload().
208 class MockDriveServiceNoConnectionAtInitiate
: public DummyDriveService
{
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();
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();
233 // Should not be used.
234 virtual CancelCallback
ResumeUpload(
235 const GURL
& upload_url
,
236 int64 start_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
{
244 return CancelCallback();
248 // Mock DriveService that returns a failure at ResumeUpload().
249 class 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();
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();
275 virtual CancelCallback
ResumeUpload(
276 const GURL
& upload_url
,
277 int64 start_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
,
286 UploadRangeResponse(GDATA_NO_CONNECTION
, -1, -1),
287 base::Passed(scoped_ptr
<ResourceEntry
>())));
288 return CancelCallback();
292 // Mock DriveService that returns a failure at GetUploadStatus().
293 class MockDriveServiceNoConnectionAtGetUploadStatus
: public DummyDriveService
{
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
,
301 UploadRangeResponse(GDATA_NO_CONNECTION
, -1, -1),
302 base::Passed(scoped_ptr
<ResourceEntry
>())));
303 return CancelCallback();
307 class DriveUploaderTest
: public testing::Test
{
309 virtual void SetUp() OVERRIDE
{
310 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
314 content::TestBrowserThreadBundle thread_bundle_
;
315 base::ScopedTempDir temp_dir_
;
320 TEST_F(DriveUploaderTest
, UploadExisting0KB
) {
321 base::FilePath local_path
;
323 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
324 temp_dir_
.path(), 0, &local_path
, &data
));
326 GDataErrorCode error
= GDATA_OTHER_ERROR
;
327 GURL upload_location
;
328 scoped_ptr
<ResourceEntry
> resource_entry
;
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
,
338 DriveUploader::UploadExistingFileOptions(),
339 test_util::CreateCopyResultCallback(
340 &error
, &upload_location
, &resource_entry
),
341 base::Bind(&test_util::AppendProgressCallbackResult
,
342 &upload_progress_values
));
343 base::RunLoop().RunUntilIdle();
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(resource_entry
);
350 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
351 ASSERT_EQ(1U, upload_progress_values
.size());
352 EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values
[0]);
355 TEST_F(DriveUploaderTest
, UploadExisting512KB
) {
356 base::FilePath local_path
;
358 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
359 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
361 GDataErrorCode error
= GDATA_OTHER_ERROR
;
362 GURL upload_location
;
363 scoped_ptr
<ResourceEntry
> resource_entry
;
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
,
373 DriveUploader::UploadExistingFileOptions(),
374 test_util::CreateCopyResultCallback(
375 &error
, &upload_location
, &resource_entry
),
376 base::Bind(&test_util::AppendProgressCallbackResult
,
377 &upload_progress_values
));
378 base::RunLoop().RunUntilIdle();
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(resource_entry
);
386 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
387 ASSERT_EQ(1U, upload_progress_values
.size());
388 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
389 upload_progress_values
[0]);
392 TEST_F(DriveUploaderTest
, InitiateUploadFail
) {
393 base::FilePath local_path
;
395 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
396 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
398 GDataErrorCode error
= HTTP_SUCCESS
;
399 GURL upload_location
;
400 scoped_ptr
<ResourceEntry
> resource_entry
;
402 MockDriveServiceNoConnectionAtInitiate mock_service
;
403 DriveUploader
uploader(&mock_service
,
404 base::MessageLoopProxy::current().get());
405 uploader
.UploadExistingFile(kTestInitiateUploadResourceId
,
408 DriveUploader::UploadExistingFileOptions(),
409 test_util::CreateCopyResultCallback(
410 &error
, &upload_location
, &resource_entry
),
411 google_apis::ProgressCallback());
412 base::RunLoop().RunUntilIdle();
414 EXPECT_EQ(GDATA_NO_CONNECTION
, error
);
415 EXPECT_TRUE(upload_location
.is_empty());
416 EXPECT_FALSE(resource_entry
);
419 TEST_F(DriveUploaderTest
, InitiateUploadNoConflict
) {
420 base::FilePath local_path
;
422 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
423 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
425 GDataErrorCode error
= GDATA_OTHER_ERROR
;
426 GURL upload_location
;
427 scoped_ptr
<ResourceEntry
> resource_entry
;
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
,
438 test_util::CreateCopyResultCallback(
439 &error
, &upload_location
, &resource_entry
),
440 google_apis::ProgressCallback());
441 base::RunLoop().RunUntilIdle();
443 EXPECT_EQ(HTTP_SUCCESS
, error
);
444 EXPECT_TRUE(upload_location
.is_empty());
447 TEST_F(DriveUploaderTest
, InitiateUploadConflict
) {
448 base::FilePath local_path
;
450 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
451 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
452 const std::string
kDestinationETag("destination_etag");
454 GDataErrorCode error
= GDATA_OTHER_ERROR
;
455 GURL upload_location
;
456 scoped_ptr
<ResourceEntry
> resource_entry
;
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
,
467 test_util::CreateCopyResultCallback(
468 &error
, &upload_location
, &resource_entry
),
469 google_apis::ProgressCallback());
470 base::RunLoop().RunUntilIdle();
472 EXPECT_EQ(HTTP_CONFLICT
, error
);
473 EXPECT_TRUE(upload_location
.is_empty());
476 TEST_F(DriveUploaderTest
, ResumeUploadFail
) {
477 base::FilePath local_path
;
479 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
480 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
482 GDataErrorCode error
= HTTP_SUCCESS
;
483 GURL upload_location
;
484 scoped_ptr
<ResourceEntry
> resource_entry
;
486 MockDriveServiceNoConnectionAtResume mock_service
;
487 DriveUploader
uploader(&mock_service
,
488 base::MessageLoopProxy::current().get());
489 uploader
.UploadExistingFile(kTestInitiateUploadResourceId
,
492 DriveUploader::UploadExistingFileOptions(),
493 test_util::CreateCopyResultCallback(
494 &error
, &upload_location
, &resource_entry
),
495 google_apis::ProgressCallback());
496 base::RunLoop().RunUntilIdle();
498 EXPECT_EQ(GDATA_NO_CONNECTION
, error
);
499 EXPECT_EQ(GURL(kTestUploadExistingFileURL
), upload_location
);
502 TEST_F(DriveUploaderTest
, GetUploadStatusFail
) {
503 base::FilePath local_path
;
505 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
506 temp_dir_
.path(), 512 * 1024, &local_path
, &data
));
508 GDataErrorCode error
= HTTP_SUCCESS
;
509 GURL upload_location
;
510 scoped_ptr
<ResourceEntry
> resource_entry
;
512 MockDriveServiceNoConnectionAtGetUploadStatus mock_service
;
513 DriveUploader
uploader(&mock_service
,
514 base::MessageLoopProxy::current().get());
515 uploader
.ResumeUploadFile(GURL(kTestUploadExistingFileURL
),
518 test_util::CreateCopyResultCallback(
519 &error
, &upload_location
, &resource_entry
),
520 google_apis::ProgressCallback());
521 base::RunLoop().RunUntilIdle();
523 EXPECT_EQ(GDATA_NO_CONNECTION
, error
);
524 EXPECT_TRUE(upload_location
.is_empty());
527 TEST_F(DriveUploaderTest
, NonExistingSourceFile
) {
528 GDataErrorCode error
= GDATA_OTHER_ERROR
;
529 GURL upload_location
;
530 scoped_ptr
<ResourceEntry
> resource_entry
;
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_"),
538 DriveUploader::UploadExistingFileOptions(),
539 test_util::CreateCopyResultCallback(
540 &error
, &upload_location
, &resource_entry
),
541 google_apis::ProgressCallback());
542 base::RunLoop().RunUntilIdle();
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());
549 TEST_F(DriveUploaderTest
, ResumeUpload
) {
550 base::FilePath local_path
;
552 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
553 temp_dir_
.path(), 1024 * 1024, &local_path
, &data
));
555 GDataErrorCode error
= GDATA_OTHER_ERROR
;
556 GURL upload_location
;
557 scoped_ptr
<ResourceEntry
> resource_entry
;
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);
566 std::vector
<test_util::ProgressInfo
> upload_progress_values
;
567 uploader
.ResumeUploadFile(
568 GURL(kTestUploadExistingFileURL
),
571 test_util::CreateCopyResultCallback(
572 &error
, &upload_location
, &resource_entry
),
573 base::Bind(&test_util::AppendProgressCallbackResult
,
574 &upload_progress_values
));
575 base::RunLoop().RunUntilIdle();
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(resource_entry
);
582 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
583 ASSERT_EQ(1U, upload_progress_values
.size());
584 EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024),
585 upload_progress_values
[0]);