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/google_apis/drive_uploader.h"
13 #include "base/bind.h"
14 #include "base/file_util.h"
15 #include "base/files/scoped_temp_dir.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop.h"
18 #include "base/values.h"
19 #include "chrome/browser/google_apis/dummy_drive_service.h"
20 #include "chrome/browser/google_apis/test_util.h"
21 #include "content/public/test/test_browser_thread.h"
22 #include "net/base/io_buffer.h"
23 #include "testing/gtest/include/gtest/gtest.h"
25 namespace google_apis
{
29 const char kTestDummyId
[] = "file:dummy_id";
30 const char kTestDocumentTitle
[] = "Hello world";
31 const char kTestDrivePath
[] = "drive/dummy.txt";
32 const char kTestInitiateUploadParentResourceId
[] = "parent_resource_id";
33 const char kTestInitiateUploadResourceId
[] = "resource_id";
34 const char kTestMimeType
[] = "text/plain";
35 const char kTestUploadURL
[] = "http://test/upload_location";
36 const int64 kUploadChunkSize
= 512 * 1024;
37 const char kTestETag
[] = "test_etag";
39 // Creates a |size| byte file and returns its |path|. The file is filled with
40 // random bytes so that the test assertions can identify correct
41 // portion of the file is being sent.
42 bool CreateFileOfSpecifiedSize(const base::FilePath
& temp_dir
,
47 for (size_t i
= 0; i
< size
; ++i
)
48 (*data
)[i
] = static_cast<char>(rand() % 256); // NOLINT
49 if (!file_util::CreateTemporaryFileInDir(temp_dir
, path
))
51 return file_util::WriteFile(*path
, data
->c_str(), static_cast<int>(size
)) ==
52 static_cast<int>(size
);
55 // Mock DriveService that verifies if the uploaded content matches the preset
57 class MockDriveServiceWithUploadExpectation
: public DummyDriveService
{
59 // Sets up an expected upload content. InitiateUpload and ResumeUpload will
60 // verify that the specified data is correctly uploaded.
61 explicit MockDriveServiceWithUploadExpectation(
62 const std::string
& expected_upload_content
)
63 : expected_upload_content_(expected_upload_content
),
65 resume_upload_call_count_(0) {}
67 int64
received_bytes() const { return received_bytes_
; }
69 int64
resume_upload_call_count() const { return resume_upload_call_count_
; }
72 // DriveServiceInterface overrides.
73 // Handles a request for obtaining an upload location URL.
74 virtual void InitiateUploadNewFile(
75 const base::FilePath
& drive_file_path
,
76 const std::string
& content_type
,
78 const std::string
& parent_resource_id
,
79 const std::string
& title
,
80 const InitiateUploadCallback
& callback
) OVERRIDE
{
81 EXPECT_EQ(kTestDocumentTitle
, title
);
82 EXPECT_EQ(kTestMimeType
, content_type
);
83 const int64 expected_size
= expected_upload_content_
.size();
84 EXPECT_EQ(expected_size
, content_length
);
85 EXPECT_EQ(kTestInitiateUploadParentResourceId
, parent_resource_id
);
87 // Calls back the upload URL for subsequent ResumeUpload operations.
88 // InitiateUpload is an asynchronous function, so don't callback directly.
89 MessageLoop::current()->PostTask(FROM_HERE
,
90 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadURL
)));
93 virtual void InitiateUploadExistingFile(
94 const base::FilePath
& drive_file_path
,
95 const std::string
& content_type
,
97 const std::string
& resource_id
,
98 const std::string
& etag
,
99 const InitiateUploadCallback
& callback
) OVERRIDE
{
100 EXPECT_EQ(kTestMimeType
, content_type
);
101 const int64 expected_size
= expected_upload_content_
.size();
102 EXPECT_EQ(expected_size
, content_length
);
103 EXPECT_EQ(kTestInitiateUploadResourceId
, resource_id
);
105 if (!etag
.empty() && etag
!= kTestETag
) {
106 MessageLoop::current()->PostTask(FROM_HERE
,
107 base::Bind(callback
, HTTP_PRECONDITION
, GURL()));
111 // Calls back the upload URL for subsequent ResumeUpload operations.
112 // InitiateUpload is an asynchronous function, so don't callback directly.
113 MessageLoop::current()->PostTask(FROM_HERE
,
114 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadURL
)));
117 // Handles a request for uploading a chunk of bytes.
118 virtual void ResumeUpload(
119 UploadMode upload_mode
,
120 const base::FilePath
& drive_file_path
,
121 const GURL
& upload_url
,
122 int64 start_position
,
124 int64 content_length
,
125 const std::string
& content_type
,
126 const scoped_refptr
<net::IOBuffer
>& buf
,
127 const UploadRangeCallback
& callback
,
128 const ProgressCallback
& progress_callback
) OVERRIDE
{
129 const int64 expected_size
= expected_upload_content_
.size();
131 // The upload range should start from the current first unreceived byte.
132 EXPECT_EQ(received_bytes_
, start_position
);
134 // The upload data must be split into 512KB chunks.
135 const int64 expected_chunk_end
=
136 std::min(received_bytes_
+ kUploadChunkSize
, expected_size
);
137 EXPECT_EQ(expected_chunk_end
, end_position
);
139 const int64 expected_chunk_size
= expected_chunk_end
- received_bytes_
;
140 const std::string
expected_chunk_data(
141 expected_upload_content_
.substr(received_bytes_
,
142 expected_chunk_size
));
143 std::string
uploading_data(buf
->data(), buf
->data() + expected_chunk_size
);
144 EXPECT_EQ(expected_chunk_data
, uploading_data
);
146 // The upload URL returned by InitiateUpload() must be used.
147 EXPECT_EQ(GURL(kTestUploadURL
), upload_url
);
149 // Other parameters should be the exact values passed to DriveUploader.
150 EXPECT_EQ(expected_size
, content_length
);
151 EXPECT_EQ(kTestMimeType
, content_type
);
153 // Update the internal status of the current upload session.
154 resume_upload_call_count_
++;
155 received_bytes_
= end_position
;
158 if (!progress_callback
.is_null()) {
159 // For the testing purpose, it always notifies the progress at the end of
160 // each chunk uploading.
161 MessageLoop::current()->PostTask(FROM_HERE
,
162 base::Bind(progress_callback
, expected_chunk_size
,
163 expected_chunk_size
));
166 // Callback with response.
167 UploadRangeResponse response
;
168 scoped_ptr
<ResourceEntry
> entry
;
169 if (received_bytes_
== content_length
) {
170 response
= UploadRangeResponse(
171 upload_mode
== UPLOAD_NEW_FILE
? HTTP_CREATED
: HTTP_SUCCESS
,
174 base::DictionaryValue dict
;
175 dict
.Set("id.$t", new base::StringValue(kTestDummyId
));
176 entry
= ResourceEntry::CreateFrom(dict
);
178 response
= UploadRangeResponse(HTTP_RESUME_INCOMPLETE
, 0, end_position
);
180 // ResumeUpload is an asynchronous function, so don't callback directly.
181 MessageLoop::current()->PostTask(FROM_HERE
,
182 base::Bind(callback
, response
, base::Passed(&entry
)));
185 std::string expected_upload_content_
;
186 int64 received_bytes_
;
187 int64 resume_upload_call_count_
;
190 // Mock DriveService that returns a failure at InitiateUpload().
191 class MockDriveServiceNoConnectionAtInitiate
: public DummyDriveService
{
193 virtual void InitiateUploadNewFile(
194 const base::FilePath
& drive_file_path
,
195 const std::string
& content_type
,
196 int64 content_length
,
197 const std::string
& parent_resource_id
,
198 const std::string
& title
,
199 const InitiateUploadCallback
& callback
) OVERRIDE
{
200 MessageLoop::current()->PostTask(FROM_HERE
,
201 base::Bind(callback
, GDATA_NO_CONNECTION
, GURL()));
204 virtual void InitiateUploadExistingFile(
205 const base::FilePath
& drive_file_path
,
206 const std::string
& content_type
,
207 int64 content_length
,
208 const std::string
& resource_id
,
209 const std::string
& etag
,
210 const InitiateUploadCallback
& callback
) OVERRIDE
{
211 MessageLoop::current()->PostTask(FROM_HERE
,
212 base::Bind(callback
, GDATA_NO_CONNECTION
, GURL()));
215 // Should not be used.
216 virtual void ResumeUpload(
217 UploadMode upload_mode
,
218 const base::FilePath
& drive_file_path
,
219 const GURL
& upload_url
,
220 int64 start_position
,
222 int64 content_length
,
223 const std::string
& content_type
,
224 const scoped_refptr
<net::IOBuffer
>& buf
,
225 const UploadRangeCallback
& callback
,
226 const ProgressCallback
& progress_callback
) OVERRIDE
{
231 // Mock DriveService that returns a failure at ResumeUpload().
232 class MockDriveServiceNoConnectionAtResume
: public DummyDriveService
{
233 // Succeeds and returns an upload location URL.
234 virtual void InitiateUploadNewFile(
235 const base::FilePath
& drive_file_path
,
236 const std::string
& content_type
,
237 int64 content_length
,
238 const std::string
& parent_resource_id
,
239 const std::string
& title
,
240 const InitiateUploadCallback
& callback
) OVERRIDE
{
241 MessageLoop::current()->PostTask(FROM_HERE
,
242 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadURL
)));
245 virtual void InitiateUploadExistingFile(
246 const base::FilePath
& drive_file_path
,
247 const std::string
& content_type
,
248 int64 content_length
,
249 const std::string
& resource_id
,
250 const std::string
& etag
,
251 const InitiateUploadCallback
& callback
) OVERRIDE
{
252 MessageLoop::current()->PostTask(FROM_HERE
,
253 base::Bind(callback
, HTTP_SUCCESS
, GURL(kTestUploadURL
)));
257 virtual void ResumeUpload(
258 UploadMode upload_mode
,
259 const base::FilePath
& drive_file_path
,
260 const GURL
& upload_url
,
261 int64 start_position
,
263 int64 content_length
,
264 const std::string
& content_type
,
265 const scoped_refptr
<net::IOBuffer
>& buf
,
266 const UploadRangeCallback
& callback
,
267 const ProgressCallback
& progress_callback
) OVERRIDE
{
268 MessageLoop::current()->PostTask(FROM_HERE
,
270 UploadRangeResponse(GDATA_NO_CONNECTION
, -1, -1),
271 base::Passed(scoped_ptr
<ResourceEntry
>())));
275 class DriveUploaderTest
: public testing::Test
{
278 : ui_thread_(content::BrowserThread::UI
, &message_loop_
) {
281 virtual void SetUp() OVERRIDE
{
282 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
285 virtual void TearDown() OVERRIDE
{
286 ASSERT_TRUE(temp_dir_
.Delete());
290 MessageLoopForUI message_loop_
;
291 content::TestBrowserThread ui_thread_
;
292 base::ScopedTempDir temp_dir_
;
297 TEST_F(DriveUploaderTest
, UploadExisting0KB
) {
298 base::FilePath local_path
;
300 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 0,
301 &local_path
, &data
));
303 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
304 base::FilePath drive_path
;
305 base::FilePath file_path
;
306 scoped_ptr
<ResourceEntry
> resource_entry
;
308 MockDriveServiceWithUploadExpectation
mock_service(data
);
309 DriveUploader
uploader(&mock_service
);
310 std::vector
<test_util::ProgressInfo
> upload_progress_values
;
311 uploader
.UploadExistingFile(
312 kTestInitiateUploadResourceId
,
313 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
316 std::string(), // etag
317 test_util::CreateCopyResultCallback(
318 &error
, &drive_path
, &file_path
, &resource_entry
),
319 base::Bind(&test_util::AppendProgressCallbackResult
,
320 &upload_progress_values
));
321 test_util::RunBlockingPoolTask();
323 EXPECT_EQ(1, mock_service
.resume_upload_call_count());
324 EXPECT_EQ(0, mock_service
.received_bytes());
325 EXPECT_EQ(DRIVE_UPLOAD_OK
, error
);
326 EXPECT_EQ(base::FilePath::FromUTF8Unsafe(kTestDrivePath
), drive_path
);
327 EXPECT_EQ(local_path
, file_path
);
328 ASSERT_TRUE(resource_entry
);
329 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
330 ASSERT_EQ(1U, upload_progress_values
.size());
331 EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values
[0]);
334 TEST_F(DriveUploaderTest
, UploadExisting512KB
) {
335 base::FilePath local_path
;
337 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 512 * 1024,
338 &local_path
, &data
));
340 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
341 base::FilePath drive_path
;
342 base::FilePath file_path
;
343 scoped_ptr
<ResourceEntry
> resource_entry
;
345 MockDriveServiceWithUploadExpectation
mock_service(data
);
346 DriveUploader
uploader(&mock_service
);
347 std::vector
<test_util::ProgressInfo
> upload_progress_values
;
348 uploader
.UploadExistingFile(
349 kTestInitiateUploadResourceId
,
350 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
353 std::string(), // etag
354 test_util::CreateCopyResultCallback(
355 &error
, &drive_path
, &file_path
, &resource_entry
),
356 base::Bind(&test_util::AppendProgressCallbackResult
,
357 &upload_progress_values
));
358 test_util::RunBlockingPoolTask();
360 // 512KB upload should not be split into multiple chunks.
361 EXPECT_EQ(1, mock_service
.resume_upload_call_count());
362 EXPECT_EQ(512 * 1024, mock_service
.received_bytes());
363 EXPECT_EQ(DRIVE_UPLOAD_OK
, error
);
364 EXPECT_EQ(base::FilePath::FromUTF8Unsafe(kTestDrivePath
), drive_path
);
365 EXPECT_EQ(local_path
, file_path
);
366 ASSERT_TRUE(resource_entry
);
367 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
368 ASSERT_EQ(1U, upload_progress_values
.size());
369 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
370 upload_progress_values
[0]);
373 TEST_F(DriveUploaderTest
, UploadExisting1234KB
) {
374 base::FilePath local_path
;
376 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 1234 * 1024,
377 &local_path
, &data
));
379 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
380 base::FilePath drive_path
;
381 base::FilePath file_path
;
382 scoped_ptr
<ResourceEntry
> resource_entry
;
384 MockDriveServiceWithUploadExpectation
mock_service(data
);
385 DriveUploader
uploader(&mock_service
);
386 std::vector
<test_util::ProgressInfo
> upload_progress_values
;
387 uploader
.UploadExistingFile(
388 kTestInitiateUploadResourceId
,
389 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
392 std::string(), // etag
393 test_util::CreateCopyResultCallback(
394 &error
, &drive_path
, &file_path
, &resource_entry
),
395 base::Bind(&test_util::AppendProgressCallbackResult
,
396 &upload_progress_values
));
397 test_util::RunBlockingPoolTask();
399 // The file should be split into 3 chunks (1234 = 512 + 512 + 210).
400 EXPECT_EQ(3, mock_service
.resume_upload_call_count());
401 EXPECT_EQ(1234 * 1024, mock_service
.received_bytes());
402 EXPECT_EQ(DRIVE_UPLOAD_OK
, error
);
403 EXPECT_EQ(base::FilePath::FromUTF8Unsafe(kTestDrivePath
), drive_path
);
404 EXPECT_EQ(local_path
, file_path
);
405 ASSERT_TRUE(resource_entry
);
406 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
407 // It is the duty of DriveUploader to accumulate up the progress value.
408 ASSERT_EQ(3U, upload_progress_values
.size());
409 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 1234 * 1024),
410 upload_progress_values
[0]);
411 EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1234 * 1024),
412 upload_progress_values
[1]);
413 EXPECT_EQ(test_util::ProgressInfo(1234 * 1024, 1234 * 1024),
414 upload_progress_values
[2]);
417 TEST_F(DriveUploaderTest
, UploadNew1234KB
) {
418 base::FilePath local_path
;
420 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 1234 * 1024,
421 &local_path
, &data
));
423 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
424 base::FilePath drive_path
;
425 base::FilePath file_path
;
426 scoped_ptr
<ResourceEntry
> resource_entry
;
428 MockDriveServiceWithUploadExpectation
mock_service(data
);
429 DriveUploader
uploader(&mock_service
);
430 uploader
.UploadNewFile(
431 kTestInitiateUploadParentResourceId
,
432 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
436 test_util::CreateCopyResultCallback(
437 &error
, &drive_path
, &file_path
, &resource_entry
),
438 google_apis::ProgressCallback());
439 test_util::RunBlockingPoolTask();
441 // The file should be split into 3 chunks (1234 = 512 + 512 + 210).
442 EXPECT_EQ(3, mock_service
.resume_upload_call_count());
443 EXPECT_EQ(1234 * 1024, mock_service
.received_bytes());
444 EXPECT_EQ(DRIVE_UPLOAD_OK
, error
);
445 EXPECT_EQ(base::FilePath::FromUTF8Unsafe(kTestDrivePath
), drive_path
);
446 EXPECT_EQ(local_path
, file_path
);
447 ASSERT_TRUE(resource_entry
);
448 EXPECT_EQ(kTestDummyId
, resource_entry
->id());
451 TEST_F(DriveUploaderTest
, InitiateUploadFail
) {
452 base::FilePath local_path
;
454 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 512 * 1024,
455 &local_path
, &data
));
457 DriveUploadError error
= DRIVE_UPLOAD_OK
;
458 base::FilePath drive_path
;
459 base::FilePath file_path
;
460 scoped_ptr
<ResourceEntry
> resource_entry
;
462 MockDriveServiceNoConnectionAtInitiate mock_service
;
463 DriveUploader
uploader(&mock_service
);
464 uploader
.UploadExistingFile(
465 kTestInitiateUploadResourceId
,
466 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
469 std::string(), // etag
470 test_util::CreateCopyResultCallback(
471 &error
, &drive_path
, &file_path
, &resource_entry
),
472 google_apis::ProgressCallback());
473 test_util::RunBlockingPoolTask();
475 EXPECT_EQ(DRIVE_UPLOAD_ERROR_ABORT
, error
);
478 TEST_F(DriveUploaderTest
, InitiateUploadNoConflict
) {
479 base::FilePath local_path
;
481 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 512 * 1024,
482 &local_path
, &data
));
484 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
485 base::FilePath drive_path
;
486 base::FilePath file_path
;
487 scoped_ptr
<ResourceEntry
> resource_entry
;
489 MockDriveServiceWithUploadExpectation
mock_service(data
);
490 DriveUploader
uploader(&mock_service
);
491 uploader
.UploadExistingFile(
492 kTestInitiateUploadResourceId
,
493 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
497 test_util::CreateCopyResultCallback(
498 &error
, &drive_path
, &file_path
, &resource_entry
),
499 google_apis::ProgressCallback());
500 test_util::RunBlockingPoolTask();
502 EXPECT_EQ(DRIVE_UPLOAD_OK
, error
);
505 TEST_F(DriveUploaderTest
, InitiateUploadConflict
) {
506 base::FilePath local_path
;
508 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 512 * 1024,
509 &local_path
, &data
));
510 const std::string
kDestinationETag("destination_etag");
512 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
513 base::FilePath drive_path
;
514 base::FilePath file_path
;
515 scoped_ptr
<ResourceEntry
> resource_entry
;
517 MockDriveServiceWithUploadExpectation
mock_service(data
);
518 DriveUploader
uploader(&mock_service
);
519 uploader
.UploadExistingFile(
520 kTestInitiateUploadResourceId
,
521 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
525 test_util::CreateCopyResultCallback(
526 &error
, &drive_path
, &file_path
, &resource_entry
),
527 google_apis::ProgressCallback());
528 test_util::RunBlockingPoolTask();
530 EXPECT_EQ(DRIVE_UPLOAD_ERROR_CONFLICT
, error
);
533 TEST_F(DriveUploaderTest
, ResumeUploadFail
) {
534 base::FilePath local_path
;
536 ASSERT_TRUE(CreateFileOfSpecifiedSize(temp_dir_
.path(), 512 * 1024,
537 &local_path
, &data
));
539 DriveUploadError error
= DRIVE_UPLOAD_OK
;
540 base::FilePath drive_path
;
541 base::FilePath file_path
;
542 scoped_ptr
<ResourceEntry
> resource_entry
;
544 MockDriveServiceNoConnectionAtResume mock_service
;
545 DriveUploader
uploader(&mock_service
);
546 uploader
.UploadExistingFile(
547 kTestInitiateUploadResourceId
,
548 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
551 std::string(), // etag
552 test_util::CreateCopyResultCallback(
553 &error
, &drive_path
, &file_path
, &resource_entry
),
554 google_apis::ProgressCallback());
555 test_util::RunBlockingPoolTask();
557 EXPECT_EQ(DRIVE_UPLOAD_ERROR_ABORT
, error
);
560 TEST_F(DriveUploaderTest
, NonExistingSourceFile
) {
561 DriveUploadError error
= DRIVE_UPLOAD_ERROR_ABORT
;
562 base::FilePath drive_path
;
563 base::FilePath file_path
;
564 scoped_ptr
<ResourceEntry
> resource_entry
;
566 DriveUploader
uploader(NULL
); // NULL, the service won't be used.
567 uploader
.UploadExistingFile(
568 kTestInitiateUploadResourceId
,
569 base::FilePath::FromUTF8Unsafe(kTestDrivePath
),
570 temp_dir_
.path().AppendASCII("_this_path_should_not_exist_"),
572 std::string(), // etag
573 test_util::CreateCopyResultCallback(
574 &error
, &drive_path
, &file_path
, &resource_entry
),
575 google_apis::ProgressCallback());
576 test_util::RunBlockingPoolTask();
578 // Should return failure without doing any attempt to connect to the server.
579 EXPECT_EQ(DRIVE_UPLOAD_ERROR_NOT_FOUND
, error
);
582 } // namespace google_apis