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 "components/drive/drive_uploader.h"
6
7 #include <stddef.h>
8 #include <stdint.h>
9
10 #include <algorithm>
11 #include <memory>
12 #include <string>
13 #include <utility>
14 #include <vector>
15
16 #include "base/bind.h"
17 #include "base/files/scoped_temp_dir.h"
18 #include "base/run_loop.h"
19 #include "base/test/task_environment.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "base/values.h"
22 #include "components/drive/service/dummy_drive_service.h"
23 #include "google_apis/drive/drive_api_parser.h"
24 #include "google_apis/drive/test_util.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26
27 using google_apis::CancelCallback;
28 using google_apis::CancelCallbackRepeating;
29 using google_apis::DRIVE_NO_CONNECTION;
30 using google_apis::DRIVE_OTHER_ERROR;
31 using google_apis::DriveApiErrorCode;
32 using google_apis::FileResource;
33 using google_apis::HTTP_CONFLICT;
34 using google_apis::HTTP_CREATED;
35 using google_apis::HTTP_NOT_FOUND;
36 using google_apis::HTTP_PRECONDITION;
37 using google_apis::HTTP_RESUME_INCOMPLETE;
38 using google_apis::HTTP_SUCCESS;
39 using google_apis::InitiateUploadCallback;
40 using google_apis::ProgressCallback;
41 using google_apis::UploadRangeResponse;
42 using google_apis::drive::UploadRangeCallback;
43 namespace test_util = google_apis::test_util;
44
45 namespace drive {
46
47 namespace {
48
49 const char kTestDummyMd5[] = "dummy_md5";
50 const char kTestDocumentTitle[] = "Hello world";
51 const char kTestInitiateUploadParentResourceId[] = "parent_resource_id";
52 const char kTestInitiateUploadResourceId[] = "resource_id";
53 const char kTestMimeType[] = "text/plain";
54 const char kTestUploadNewFileURL[] = "http://test/upload_location/new_file";
55 const char kTestUploadExistingFileURL[] =
56 "http://test/upload_location/existing_file";
57 const int64_t kUploadChunkSize = 1024 * 1024 * 1024;
58 const char kTestETag[] = "test_etag";
59
SendMultipartUploadResult(DriveApiErrorCode response_code,int64_t content_length,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)60 CancelCallbackRepeating SendMultipartUploadResult(
61 DriveApiErrorCode response_code,
62 int64_t content_length,
63 google_apis::FileResourceCallback callback,
64 google_apis::ProgressCallback progress_callback) {
65 // Callback progress
66 if (!progress_callback.is_null()) {
67 // For the testing purpose, it always notifies the progress at the end of
68 // whole file uploading.
69 base::ThreadTaskRunnerHandle::Get()->PostTask(
70 FROM_HERE,
71 base::BindOnce(progress_callback, content_length, content_length));
72 }
73
74 // MultipartUploadXXXFile is an asynchronous function, so don't callback
75 // directly.
76 std::unique_ptr<FileResource> entry;
77 entry = std::make_unique<FileResource>();
78 entry->set_md5_checksum(kTestDummyMd5);
79 base::ThreadTaskRunnerHandle::Get()->PostTask(
80 FROM_HERE,
81 base::BindOnce(std::move(callback), response_code, std::move(entry)));
82 return CancelCallbackRepeating();
83 }
84
85 // Mock DriveService that verifies if the uploaded content matches the preset
86 // expectation.
87 class MockDriveServiceWithUploadExpectation : public DummyDriveService {
88 public:
89 // Sets up an expected upload content. InitiateUpload and ResumeUpload will
90 // verify that the specified data is correctly uploaded.
MockDriveServiceWithUploadExpectation(const base::FilePath & expected_upload_file,int64_t expected_content_length)91 MockDriveServiceWithUploadExpectation(
92 const base::FilePath& expected_upload_file,
93 int64_t expected_content_length)
94 : expected_upload_file_(expected_upload_file),
95 expected_content_length_(expected_content_length),
96 received_bytes_(0),
97 resume_upload_call_count_(0),
98 multipart_upload_call_count_(0) {}
99
received_bytes() const100 int64_t received_bytes() const { return received_bytes_; }
set_received_bytes(int64_t received_bytes)101 void set_received_bytes(int64_t received_bytes) {
102 received_bytes_ = received_bytes;
103 }
104
resume_upload_call_count() const105 int64_t resume_upload_call_count() const { return resume_upload_call_count_; }
multipart_upload_call_count() const106 int64_t multipart_upload_call_count() const {
107 return multipart_upload_call_count_;
108 }
109
110 private:
111 // DriveServiceInterface overrides.
112 // Handles a request for obtaining an upload location URL.
InitiateUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const UploadNewFileOptions & options,const InitiateUploadCallback & callback)113 CancelCallback InitiateUploadNewFile(
114 const std::string& content_type,
115 int64_t content_length,
116 const std::string& parent_resource_id,
117 const std::string& title,
118 const UploadNewFileOptions& options,
119 const InitiateUploadCallback& callback) override {
120 EXPECT_EQ(kTestDocumentTitle, title);
121 EXPECT_EQ(kTestMimeType, content_type);
122 EXPECT_EQ(expected_content_length_, content_length);
123 EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
124
125 // Calls back the upload URL for subsequent ResumeUpload requests.
126 // InitiateUpload is an asynchronous function, so don't callback directly.
127 base::ThreadTaskRunnerHandle::Get()->PostTask(
128 FROM_HERE,
129 base::BindOnce(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
130 return CancelCallback();
131 }
132
InitiateUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const UploadExistingFileOptions & options,const InitiateUploadCallback & callback)133 CancelCallback InitiateUploadExistingFile(
134 const std::string& content_type,
135 int64_t content_length,
136 const std::string& resource_id,
137 const UploadExistingFileOptions& options,
138 const InitiateUploadCallback& callback) override {
139 EXPECT_EQ(kTestMimeType, content_type);
140 EXPECT_EQ(expected_content_length_, content_length);
141 EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
142
143 if (!options.etag.empty() && options.etag != kTestETag) {
144 base::ThreadTaskRunnerHandle::Get()->PostTask(
145 FROM_HERE, base::BindOnce(callback, HTTP_PRECONDITION, GURL()));
146 return CancelCallback();
147 }
148
149 // Calls back the upload URL for subsequent ResumeUpload requests.
150 // InitiateUpload is an asynchronous function, so don't callback directly.
151 base::ThreadTaskRunnerHandle::Get()->PostTask(
152 FROM_HERE, base::BindOnce(callback, HTTP_SUCCESS,
153 GURL(kTestUploadExistingFileURL)));
154 return CancelCallback();
155 }
156
157 // Handles a request for uploading a chunk of bytes.
ResumeUpload(const GURL & upload_location,int64_t start_position,int64_t end_position,int64_t content_length,const std::string & content_type,const base::FilePath & local_file_path,UploadRangeCallback callback,ProgressCallback progress_callback)158 CancelCallback ResumeUpload(const GURL& upload_location,
159 int64_t start_position,
160 int64_t end_position,
161 int64_t content_length,
162 const std::string& content_type,
163 const base::FilePath& local_file_path,
164 UploadRangeCallback callback,
165 ProgressCallback progress_callback) override {
166 // The upload range should start from the current first unreceived byte.
167 EXPECT_EQ(received_bytes_, start_position);
168 EXPECT_EQ(expected_upload_file_, local_file_path);
169
170 // The upload data must be split into 512KB chunks.
171 const int64_t expected_chunk_end =
172 std::min(received_bytes_ + kUploadChunkSize, expected_content_length_);
173 EXPECT_EQ(expected_chunk_end, end_position);
174
175 // The upload URL returned by InitiateUpload() must be used.
176 EXPECT_TRUE(upload_location == kTestUploadNewFileURL ||
177 upload_location == kTestUploadExistingFileURL);
178
179 // Other parameters should be the exact values passed to DriveUploader.
180 EXPECT_EQ(expected_content_length_, content_length);
181 EXPECT_EQ(kTestMimeType, content_type);
182
183 // Update the internal status of the current upload session.
184 resume_upload_call_count_++;
185 received_bytes_ = end_position;
186
187 // Callback progress
188 if (!progress_callback.is_null()) {
189 // For the testing purpose, it always notifies the progress at the end of
190 // each chunk uploading.
191 int64_t chunk_size = end_position - start_position;
192 base::ThreadTaskRunnerHandle::Get()->PostTask(
193 FROM_HERE, base::BindOnce(progress_callback, chunk_size, chunk_size));
194 }
195
196 SendUploadRangeResponse(upload_location, std::move(callback));
197 return CancelCallback();
198 }
199
200 // Handles a request to fetch the current upload status.
GetUploadStatus(const GURL & upload_location,int64_t content_length,UploadRangeCallback callback)201 CancelCallback GetUploadStatus(const GURL& upload_location,
202 int64_t content_length,
203 UploadRangeCallback callback) override {
204 EXPECT_EQ(expected_content_length_, content_length);
205 // The upload URL returned by InitiateUpload() must be used.
206 EXPECT_TRUE(upload_location == kTestUploadNewFileURL ||
207 upload_location == kTestUploadExistingFileURL);
208
209 SendUploadRangeResponse(upload_location, std::move(callback));
210 return CancelCallback();
211 }
212
213 // Runs |callback| with the current upload status.
SendUploadRangeResponse(const GURL & upload_location,UploadRangeCallback callback)214 void SendUploadRangeResponse(const GURL& upload_location,
215 UploadRangeCallback callback) {
216 // Callback with response.
217 UploadRangeResponse response;
218 std::unique_ptr<FileResource> entry;
219 if (received_bytes_ == expected_content_length_) {
220 DriveApiErrorCode response_code =
221 upload_location == kTestUploadNewFileURL ?
222 HTTP_CREATED : HTTP_SUCCESS;
223 response = UploadRangeResponse(response_code, -1, -1);
224
225 entry = std::make_unique<FileResource>();
226 entry->set_md5_checksum(kTestDummyMd5);
227 } else {
228 response = UploadRangeResponse(
229 HTTP_RESUME_INCOMPLETE, 0, received_bytes_);
230 }
231 // ResumeUpload is an asynchronous function, so don't callback directly.
232 base::ThreadTaskRunnerHandle::Get()->PostTask(
233 FROM_HERE,
234 base::BindOnce(std::move(callback), response, std::move(entry)));
235 }
236
MultipartUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const base::FilePath & local_file_path,const UploadNewFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)237 CancelCallbackRepeating MultipartUploadNewFile(
238 const std::string& content_type,
239 int64_t content_length,
240 const std::string& parent_resource_id,
241 const std::string& title,
242 const base::FilePath& local_file_path,
243 const UploadNewFileOptions& options,
244 google_apis::FileResourceCallback callback,
245 google_apis::ProgressCallback progress_callback) override {
246 EXPECT_EQ(kTestMimeType, content_type);
247 EXPECT_EQ(expected_content_length_, content_length);
248 EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
249 EXPECT_EQ(kTestDocumentTitle, title);
250 EXPECT_EQ(expected_upload_file_, local_file_path);
251
252 received_bytes_ = content_length;
253 multipart_upload_call_count_++;
254 return SendMultipartUploadResult(HTTP_CREATED, content_length,
255 std::move(callback), progress_callback);
256 }
257
MultipartUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const base::FilePath & local_file_path,const UploadExistingFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)258 CancelCallbackRepeating MultipartUploadExistingFile(
259 const std::string& content_type,
260 int64_t content_length,
261 const std::string& resource_id,
262 const base::FilePath& local_file_path,
263 const UploadExistingFileOptions& options,
264 google_apis::FileResourceCallback callback,
265 google_apis::ProgressCallback progress_callback) override {
266 EXPECT_EQ(kTestMimeType, content_type);
267 EXPECT_EQ(expected_content_length_, content_length);
268 EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
269 EXPECT_EQ(expected_upload_file_, local_file_path);
270
271 if (!options.etag.empty() && options.etag != kTestETag) {
272 base::ThreadTaskRunnerHandle::Get()->PostTask(
273 FROM_HERE,
274 base::BindOnce(std::move(callback), HTTP_PRECONDITION, nullptr));
275 return CancelCallbackRepeating();
276 }
277
278 received_bytes_ = content_length;
279 multipart_upload_call_count_++;
280 return SendMultipartUploadResult(HTTP_SUCCESS, content_length,
281 std::move(callback), progress_callback);
282 }
283
284 const base::FilePath expected_upload_file_;
285 const int64_t expected_content_length_;
286 int64_t received_bytes_;
287 int64_t resume_upload_call_count_;
288 int64_t multipart_upload_call_count_;
289 };
290
291 // Mock DriveService that returns a failure at InitiateUpload().
292 class MockDriveServiceNoConnectionAtInitiate : public DummyDriveService {
293 // Returns error.
InitiateUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const UploadNewFileOptions & options,const InitiateUploadCallback & callback)294 CancelCallback InitiateUploadNewFile(
295 const std::string& content_type,
296 int64_t content_length,
297 const std::string& parent_resource_id,
298 const std::string& title,
299 const UploadNewFileOptions& options,
300 const InitiateUploadCallback& callback) override {
301 base::ThreadTaskRunnerHandle::Get()->PostTask(
302 FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, GURL()));
303 return CancelCallback();
304 }
305
InitiateUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const UploadExistingFileOptions & options,const InitiateUploadCallback & callback)306 CancelCallback InitiateUploadExistingFile(
307 const std::string& content_type,
308 int64_t content_length,
309 const std::string& resource_id,
310 const UploadExistingFileOptions& options,
311 const InitiateUploadCallback& callback) override {
312 base::ThreadTaskRunnerHandle::Get()->PostTask(
313 FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, GURL()));
314 return CancelCallback();
315 }
316
317 // Should not be used.
ResumeUpload(const GURL & upload_url,int64_t start_position,int64_t end_position,int64_t content_length,const std::string & content_type,const base::FilePath & local_file_path,UploadRangeCallback callback,ProgressCallback progress_callback)318 CancelCallback ResumeUpload(const GURL& upload_url,
319 int64_t start_position,
320 int64_t end_position,
321 int64_t content_length,
322 const std::string& content_type,
323 const base::FilePath& local_file_path,
324 UploadRangeCallback callback,
325 ProgressCallback progress_callback) override {
326 NOTREACHED();
327 return CancelCallback();
328 }
329
MultipartUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const base::FilePath & local_file_path,const UploadNewFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)330 CancelCallbackRepeating MultipartUploadNewFile(
331 const std::string& content_type,
332 int64_t content_length,
333 const std::string& parent_resource_id,
334 const std::string& title,
335 const base::FilePath& local_file_path,
336 const UploadNewFileOptions& options,
337 google_apis::FileResourceCallback callback,
338 google_apis::ProgressCallback progress_callback) override {
339 base::ThreadTaskRunnerHandle::Get()->PostTask(
340 FROM_HERE,
341 base::BindOnce(std::move(callback), DRIVE_NO_CONNECTION, nullptr));
342 return CancelCallbackRepeating();
343 }
344
MultipartUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const base::FilePath & local_file_path,const UploadExistingFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)345 CancelCallbackRepeating MultipartUploadExistingFile(
346 const std::string& content_type,
347 int64_t content_length,
348 const std::string& resource_id,
349 const base::FilePath& local_file_path,
350 const UploadExistingFileOptions& options,
351 google_apis::FileResourceCallback callback,
352 google_apis::ProgressCallback progress_callback) override {
353 base::ThreadTaskRunnerHandle::Get()->PostTask(
354 FROM_HERE,
355 base::BindOnce(std::move(callback), DRIVE_NO_CONNECTION, nullptr));
356 return CancelCallbackRepeating();
357 }
358 };
359
360 // Mock DriveService that returns a failure at ResumeUpload().
361 class MockDriveServiceNoConnectionAtResume : public DummyDriveService {
362 // Succeeds and returns an upload location URL.
InitiateUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const UploadNewFileOptions & options,const InitiateUploadCallback & callback)363 CancelCallback InitiateUploadNewFile(
364 const std::string& content_type,
365 int64_t content_length,
366 const std::string& parent_resource_id,
367 const std::string& title,
368 const UploadNewFileOptions& options,
369 const InitiateUploadCallback& callback) override {
370 base::ThreadTaskRunnerHandle::Get()->PostTask(
371 FROM_HERE,
372 base::BindOnce(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
373 return CancelCallback();
374 }
375
InitiateUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const UploadExistingFileOptions & options,const InitiateUploadCallback & callback)376 CancelCallback InitiateUploadExistingFile(
377 const std::string& content_type,
378 int64_t content_length,
379 const std::string& resource_id,
380 const UploadExistingFileOptions& options,
381 const InitiateUploadCallback& callback) override {
382 base::ThreadTaskRunnerHandle::Get()->PostTask(
383 FROM_HERE, base::BindOnce(callback, HTTP_SUCCESS,
384 GURL(kTestUploadExistingFileURL)));
385 return CancelCallback();
386 }
387
388 // Returns error.
ResumeUpload(const GURL & upload_url,int64_t start_position,int64_t end_position,int64_t content_length,const std::string & content_type,const base::FilePath & local_file_path,UploadRangeCallback callback,ProgressCallback progress_callback)389 CancelCallback ResumeUpload(const GURL& upload_url,
390 int64_t start_position,
391 int64_t end_position,
392 int64_t content_length,
393 const std::string& content_type,
394 const base::FilePath& local_file_path,
395 UploadRangeCallback callback,
396 ProgressCallback progress_callback) override {
397 base::ThreadTaskRunnerHandle::Get()->PostTask(
398 FROM_HERE,
399 base::BindOnce(std::move(callback),
400 UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
401 nullptr));
402 return CancelCallback();
403 }
404 };
405
406 // Mock DriveService that returns a failure at GetUploadStatus().
407 class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService {
408 // Returns error.
GetUploadStatus(const GURL & upload_url,int64_t content_length,UploadRangeCallback callback)409 CancelCallback GetUploadStatus(const GURL& upload_url,
410 int64_t content_length,
411 UploadRangeCallback callback) override {
412 base::ThreadTaskRunnerHandle::Get()->PostTask(
413 FROM_HERE,
414 base::BindOnce(std::move(callback),
415 UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
416 nullptr));
417 return CancelCallback();
418 }
419 };
420
421 class DriveUploaderTest : public testing::Test {
422 public:
SetUp()423 void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
424
425 protected:
426 base::test::SingleThreadTaskEnvironment task_environment_;
427 base::ScopedTempDir temp_dir_;
428 };
429
430 } // namespace
431
TEST_F(DriveUploaderTest,UploadExisting0KB)432 TEST_F(DriveUploaderTest, UploadExisting0KB) {
433 base::FilePath local_path;
434 std::string data;
435 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(temp_dir_.GetPath(), 0,
436 &local_path, &data));
437
438 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
439 GURL upload_location;
440 std::unique_ptr<FileResource> entry;
441
442 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
443 DriveUploader uploader(&mock_service,
444 base::ThreadTaskRunnerHandle::Get().get(),
445 mojo::NullRemote());
446 std::vector<test_util::ProgressInfo> upload_progress_values;
447 uploader.UploadExistingFile(
448 kTestInitiateUploadResourceId, local_path, kTestMimeType,
449 UploadExistingFileOptions(),
450 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
451 base::BindRepeating(&test_util::AppendProgressCallbackResult,
452 &upload_progress_values));
453 base::RunLoop().RunUntilIdle();
454
455 EXPECT_EQ(0, mock_service.resume_upload_call_count());
456 EXPECT_EQ(1, mock_service.multipart_upload_call_count());
457 EXPECT_EQ(0, mock_service.received_bytes());
458 EXPECT_EQ(HTTP_SUCCESS, error);
459 EXPECT_TRUE(upload_location.is_empty());
460 ASSERT_TRUE(entry);
461 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
462 ASSERT_EQ(1U, upload_progress_values.size());
463 EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values[0]);
464 }
465
TEST_F(DriveUploaderTest,UploadExisting512KB)466 TEST_F(DriveUploaderTest, UploadExisting512KB) {
467 base::FilePath local_path;
468 std::string data;
469 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
470 temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
471
472 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
473 GURL upload_location;
474 std::unique_ptr<FileResource> entry;
475
476 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
477 DriveUploader uploader(&mock_service,
478 base::ThreadTaskRunnerHandle::Get().get(),
479 mojo::NullRemote());
480 std::vector<test_util::ProgressInfo> upload_progress_values;
481 uploader.UploadExistingFile(
482 kTestInitiateUploadResourceId, local_path, kTestMimeType,
483 UploadExistingFileOptions(),
484 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
485 base::BindRepeating(&test_util::AppendProgressCallbackResult,
486 &upload_progress_values));
487 base::RunLoop().RunUntilIdle();
488
489 // 512KB upload should be uploaded as multipart body.
490 EXPECT_EQ(0, mock_service.resume_upload_call_count());
491 EXPECT_EQ(1, mock_service.multipart_upload_call_count());
492 EXPECT_EQ(512 * 1024, mock_service.received_bytes());
493 EXPECT_EQ(HTTP_SUCCESS, error);
494 EXPECT_TRUE(upload_location.is_empty());
495 ASSERT_TRUE(entry);
496 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
497 ASSERT_EQ(1U, upload_progress_values.size());
498 EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
499 upload_progress_values[0]);
500 }
501
TEST_F(DriveUploaderTest,UploadExisting2MB)502 TEST_F(DriveUploaderTest, UploadExisting2MB) {
503 base::FilePath local_path;
504 std::string data;
505 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
506 temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
507
508 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
509 GURL upload_location;
510 std::unique_ptr<FileResource> entry;
511
512 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
513 DriveUploader uploader(&mock_service,
514 base::ThreadTaskRunnerHandle::Get().get(),
515 mojo::NullRemote());
516 std::vector<test_util::ProgressInfo> upload_progress_values;
517 uploader.UploadExistingFile(
518 kTestInitiateUploadResourceId, local_path, kTestMimeType,
519 UploadExistingFileOptions(),
520 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
521 base::BindRepeating(&test_util::AppendProgressCallbackResult,
522 &upload_progress_values));
523 base::RunLoop().RunUntilIdle();
524
525 // 2MB upload should not be split into multiple chunks.
526 EXPECT_EQ(1, mock_service.resume_upload_call_count());
527 EXPECT_EQ(0, mock_service.multipart_upload_call_count());
528 EXPECT_EQ(2 * 1024 * 1024, mock_service.received_bytes());
529 EXPECT_EQ(HTTP_SUCCESS, error);
530 EXPECT_TRUE(upload_location.is_empty());
531 ASSERT_TRUE(entry);
532 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
533 ASSERT_EQ(1U, upload_progress_values.size());
534 EXPECT_EQ(test_util::ProgressInfo(2 * 1024 * 1024, 2 * 1024 * 1024),
535 upload_progress_values[0]);
536 }
537
TEST_F(DriveUploaderTest,InitiateUploadFail)538 TEST_F(DriveUploaderTest, InitiateUploadFail) {
539 base::FilePath local_path;
540 std::string data;
541 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
542 temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
543
544 DriveApiErrorCode error = HTTP_SUCCESS;
545 GURL upload_location;
546 std::unique_ptr<FileResource> entry;
547
548 MockDriveServiceNoConnectionAtInitiate mock_service;
549 DriveUploader uploader(&mock_service,
550 base::ThreadTaskRunnerHandle::Get().get(),
551 mojo::NullRemote());
552 uploader.UploadExistingFile(
553 kTestInitiateUploadResourceId, local_path, kTestMimeType,
554 UploadExistingFileOptions(),
555 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
556 google_apis::ProgressCallback());
557 base::RunLoop().RunUntilIdle();
558
559 EXPECT_EQ(DRIVE_NO_CONNECTION, error);
560 EXPECT_TRUE(upload_location.is_empty());
561 EXPECT_FALSE(entry);
562 }
563
TEST_F(DriveUploaderTest,MultipartUploadFail)564 TEST_F(DriveUploaderTest, MultipartUploadFail) {
565 base::FilePath local_path;
566 std::string data;
567 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
568 temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
569
570 DriveApiErrorCode error = HTTP_SUCCESS;
571 GURL upload_location;
572 std::unique_ptr<FileResource> entry;
573
574 MockDriveServiceNoConnectionAtInitiate mock_service;
575 DriveUploader uploader(&mock_service,
576 base::ThreadTaskRunnerHandle::Get().get(),
577 mojo::NullRemote());
578 uploader.UploadExistingFile(
579 kTestInitiateUploadResourceId, local_path, kTestMimeType,
580 UploadExistingFileOptions(),
581 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
582 google_apis::ProgressCallback());
583 base::RunLoop().RunUntilIdle();
584
585 EXPECT_EQ(DRIVE_NO_CONNECTION, error);
586 EXPECT_TRUE(upload_location.is_empty());
587 EXPECT_FALSE(entry);
588 }
589
TEST_F(DriveUploaderTest,InitiateUploadNoConflict)590 TEST_F(DriveUploaderTest, InitiateUploadNoConflict) {
591 base::FilePath local_path;
592 std::string data;
593 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
594 temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
595
596 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
597 GURL upload_location;
598 std::unique_ptr<FileResource> entry;
599
600 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
601 DriveUploader uploader(&mock_service,
602 base::ThreadTaskRunnerHandle::Get().get(),
603 mojo::NullRemote());
604 UploadExistingFileOptions options;
605 options.etag = kTestETag;
606 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
607 local_path,
608 kTestMimeType,
609 options,
610 test_util::CreateCopyResultCallback(
611 &error, &upload_location, &entry),
612 google_apis::ProgressCallback());
613 base::RunLoop().RunUntilIdle();
614
615 EXPECT_EQ(HTTP_SUCCESS, error);
616 EXPECT_TRUE(upload_location.is_empty());
617 }
618
TEST_F(DriveUploaderTest,MultipartUploadConflict)619 TEST_F(DriveUploaderTest, MultipartUploadConflict) {
620 base::FilePath local_path;
621 std::string data;
622 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
623 temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
624 const std::string kDestinationETag("destination_etag");
625
626 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
627 GURL upload_location;
628 std::unique_ptr<FileResource> entry;
629
630 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
631 DriveUploader uploader(&mock_service,
632 base::ThreadTaskRunnerHandle::Get().get(),
633 mojo::NullRemote());
634 UploadExistingFileOptions options;
635 options.etag = kDestinationETag;
636 uploader.UploadExistingFile(kTestInitiateUploadResourceId,
637 local_path,
638 kTestMimeType,
639 options,
640 test_util::CreateCopyResultCallback(
641 &error, &upload_location, &entry),
642 google_apis::ProgressCallback());
643 base::RunLoop().RunUntilIdle();
644
645 EXPECT_EQ(HTTP_CONFLICT, error);
646 EXPECT_TRUE(upload_location.is_empty());
647 }
648
TEST_F(DriveUploaderTest,InitiateUploadConflict)649 TEST_F(DriveUploaderTest, InitiateUploadConflict) {
650 base::FilePath local_path;
651 std::string data;
652 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
653 temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
654 const std::string kDestinationETag("destination_etag");
655
656 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
657 GURL upload_location;
658 std::unique_ptr<FileResource> entry;
659
660 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
661 DriveUploader uploader(&mock_service,
662 base::ThreadTaskRunnerHandle::Get().get(),
663 mojo::NullRemote());
664 UploadExistingFileOptions options;
665 options.etag = kDestinationETag;
666 uploader.UploadExistingFile(
667 kTestInitiateUploadResourceId, local_path, kTestMimeType, options,
668 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
669 google_apis::ProgressCallback());
670 base::RunLoop().RunUntilIdle();
671
672 EXPECT_EQ(HTTP_CONFLICT, error);
673 EXPECT_TRUE(upload_location.is_empty());
674 }
675
TEST_F(DriveUploaderTest,ResumeUploadFail)676 TEST_F(DriveUploaderTest, ResumeUploadFail) {
677 base::FilePath local_path;
678 std::string data;
679 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
680 temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
681
682 DriveApiErrorCode error = HTTP_SUCCESS;
683 GURL upload_location;
684 std::unique_ptr<FileResource> entry;
685
686 MockDriveServiceNoConnectionAtResume mock_service;
687 DriveUploader uploader(&mock_service,
688 base::ThreadTaskRunnerHandle::Get().get(),
689 mojo::NullRemote());
690 uploader.UploadExistingFile(
691 kTestInitiateUploadResourceId, local_path, kTestMimeType,
692 UploadExistingFileOptions(),
693 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
694 google_apis::ProgressCallback());
695 base::RunLoop().RunUntilIdle();
696
697 EXPECT_EQ(DRIVE_NO_CONNECTION, error);
698 EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location);
699 }
700
TEST_F(DriveUploaderTest,GetUploadStatusFail)701 TEST_F(DriveUploaderTest, GetUploadStatusFail) {
702 base::FilePath local_path;
703 std::string data;
704 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
705 temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
706
707 DriveApiErrorCode error = HTTP_SUCCESS;
708 GURL upload_location;
709 std::unique_ptr<FileResource> entry;
710
711 MockDriveServiceNoConnectionAtGetUploadStatus mock_service;
712 DriveUploader uploader(&mock_service,
713 base::ThreadTaskRunnerHandle::Get().get(),
714 mojo::NullRemote());
715 uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL),
716 local_path,
717 kTestMimeType,
718 test_util::CreateCopyResultCallback(
719 &error, &upload_location, &entry),
720 google_apis::ProgressCallback());
721 base::RunLoop().RunUntilIdle();
722
723 EXPECT_EQ(DRIVE_NO_CONNECTION, error);
724 EXPECT_TRUE(upload_location.is_empty());
725 }
726
TEST_F(DriveUploaderTest,NonExistingSourceFile)727 TEST_F(DriveUploaderTest, NonExistingSourceFile) {
728 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
729 GURL upload_location;
730 std::unique_ptr<FileResource> entry;
731
732 DriveUploader uploader(nullptr, // nullptr, the service won't be used.
733 base::ThreadTaskRunnerHandle::Get().get(),
734 mojo::NullRemote());
735 uploader.UploadExistingFile(
736 kTestInitiateUploadResourceId,
737 temp_dir_.GetPath().AppendASCII("_this_path_should_not_exist_"),
738 kTestMimeType, UploadExistingFileOptions(),
739 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
740 google_apis::ProgressCallback());
741 base::RunLoop().RunUntilIdle();
742
743 // Should return failure without doing any attempt to connect to the server.
744 EXPECT_EQ(HTTP_NOT_FOUND, error);
745 EXPECT_TRUE(upload_location.is_empty());
746 }
747
TEST_F(DriveUploaderTest,ResumeUpload)748 TEST_F(DriveUploaderTest, ResumeUpload) {
749 base::FilePath local_path;
750 std::string data;
751 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
752 temp_dir_.GetPath(), 1024 * 1024, &local_path, &data));
753
754 DriveApiErrorCode error = DRIVE_OTHER_ERROR;
755 GURL upload_location;
756 std::unique_ptr<FileResource> entry;
757
758 MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
759 DriveUploader uploader(&mock_service,
760 base::ThreadTaskRunnerHandle::Get().get(),
761 mojo::NullRemote());
762 // Emulate the situation that the only first part is successfully uploaded,
763 // but not the latter half.
764 mock_service.set_received_bytes(512 * 1024);
765
766 std::vector<test_util::ProgressInfo> upload_progress_values;
767 uploader.ResumeUploadFile(
768 GURL(kTestUploadExistingFileURL), local_path, kTestMimeType,
769 test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
770 base::BindRepeating(&test_util::AppendProgressCallbackResult,
771 &upload_progress_values));
772 base::RunLoop().RunUntilIdle();
773
774 EXPECT_EQ(1, mock_service.resume_upload_call_count());
775 EXPECT_EQ(1024 * 1024, mock_service.received_bytes());
776 EXPECT_EQ(HTTP_SUCCESS, error);
777 EXPECT_TRUE(upload_location.is_empty());
778 ASSERT_TRUE(entry);
779 EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
780 ASSERT_EQ(1U, upload_progress_values.size());
781 EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024),
782 upload_progress_values[0]);
783 }
784
785 class MockDriveServiceForBatchProcessing : public DummyDriveService {
786 public:
787 struct UploadFileInfo {
788 enum { NEW_FILE, EXISTING_FILE } type;
789 std::string content_type;
790 uint64_t content_length;
791 std::string parent_resource_id;
792 std::string resource_id;
793 std::string title;
794 base::FilePath local_file_path;
795 google_apis::FileResourceCallback callback;
796 google_apis::ProgressCallback progress_callback;
797 };
798
799 class BatchRequestConfigurator : public BatchRequestConfiguratorInterface {
800 public:
BatchRequestConfigurator(MockDriveServiceForBatchProcessing * service)801 explicit BatchRequestConfigurator(
802 MockDriveServiceForBatchProcessing* service)
803 : service(service) {}
804
MultipartUploadNewFile(const std::string & content_type,int64_t content_length,const std::string & parent_resource_id,const std::string & title,const base::FilePath & local_file_path,const UploadNewFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)805 CancelCallback MultipartUploadNewFile(
806 const std::string& content_type,
807 int64_t content_length,
808 const std::string& parent_resource_id,
809 const std::string& title,
810 const base::FilePath& local_file_path,
811 const UploadNewFileOptions& options,
812 google_apis::FileResourceCallback callback,
813 google_apis::ProgressCallback progress_callback) override {
814 UploadFileInfo info;
815 info.type = UploadFileInfo::NEW_FILE;
816 info.content_type = content_type;
817 info.content_length = content_length;
818 info.parent_resource_id = parent_resource_id;
819 info.title = title;
820 info.local_file_path = local_file_path;
821 info.callback = std::move(callback);
822 info.progress_callback = progress_callback;
823 service->files.push_back(std::move(info));
824 return CancelCallback();
825 }
826
MultipartUploadExistingFile(const std::string & content_type,int64_t content_length,const std::string & resource_id,const base::FilePath & local_file_path,const UploadExistingFileOptions & options,google_apis::FileResourceCallback callback,google_apis::ProgressCallback progress_callback)827 CancelCallback MultipartUploadExistingFile(
828 const std::string& content_type,
829 int64_t content_length,
830 const std::string& resource_id,
831 const base::FilePath& local_file_path,
832 const UploadExistingFileOptions& options,
833 google_apis::FileResourceCallback callback,
834 google_apis::ProgressCallback progress_callback) override {
835 UploadFileInfo info;
836 info.type = UploadFileInfo::EXISTING_FILE;
837 info.content_type = content_type;
838 info.content_length = content_length;
839 info.resource_id = resource_id;
840 info.local_file_path = local_file_path;
841 info.callback = std::move(callback);
842 info.progress_callback = progress_callback;
843 service->files.push_back(std::move(info));
844 return CancelCallback();
845 }
846
Commit()847 void Commit() override {
848 ASSERT_FALSE(service->committed);
849 service->committed = true;
850 for (auto& file : service->files) {
851 SendMultipartUploadResult(HTTP_SUCCESS, file.content_length,
852 std::move(file.callback),
853 file.progress_callback);
854 }
855 }
856
857 private:
858 MockDriveServiceForBatchProcessing* service;
859 };
860
861 public:
StartBatchRequest()862 std::unique_ptr<BatchRequestConfiguratorInterface> StartBatchRequest()
863 override {
864 committed = false;
865 return std::unique_ptr<BatchRequestConfiguratorInterface>(
866 new BatchRequestConfigurator(this));
867 }
868
869 std::vector<UploadFileInfo> files;
870 bool committed;
871 };
872
TEST_F(DriveUploaderTest,BatchProcessing)873 TEST_F(DriveUploaderTest, BatchProcessing) {
874 // Preapre test file.
875 const size_t kTestFileSize = 1024 * 512;
876 base::FilePath local_path;
877 std::string data;
878 ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
879 temp_dir_.GetPath(), kTestFileSize, &local_path, &data));
880
881 // Prepare test target.
882 MockDriveServiceForBatchProcessing service;
883 DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get(),
884 mojo::NullRemote());
885
886 struct {
887 DriveApiErrorCode error;
888 GURL resume_url;
889 std::unique_ptr<FileResource> file;
890 UploadCompletionCallback callback() {
891 return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
892 }
893 } results[2];
894
895 uploader.StartBatchProcessing();
896 uploader.UploadNewFile("parent_resource_id", local_path, "title",
897 kTestMimeType, UploadNewFileOptions(),
898 results[0].callback(),
899 google_apis::ProgressCallback());
900 uploader.UploadExistingFile(
901 "resource_id", local_path, kTestMimeType, UploadExistingFileOptions(),
902 results[1].callback(), google_apis::ProgressCallback());
903 uploader.StopBatchProcessing();
904 base::RunLoop().RunUntilIdle();
905
906 ASSERT_EQ(2u, service.files.size());
907 EXPECT_TRUE(service.committed);
908
909 EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::NEW_FILE,
910 service.files[0].type);
911 EXPECT_EQ(kTestMimeType, service.files[0].content_type);
912 EXPECT_EQ(kTestFileSize, service.files[0].content_length);
913 EXPECT_EQ("parent_resource_id", service.files[0].parent_resource_id);
914 EXPECT_EQ("", service.files[0].resource_id);
915 EXPECT_EQ("title", service.files[0].title);
916 EXPECT_EQ(local_path.value(), service.files[0].local_file_path.value());
917
918 EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::EXISTING_FILE,
919 service.files[1].type);
920 EXPECT_EQ(kTestMimeType, service.files[1].content_type);
921 EXPECT_EQ(kTestFileSize, service.files[1].content_length);
922 EXPECT_EQ("", service.files[1].parent_resource_id);
923 EXPECT_EQ("resource_id", service.files[1].resource_id);
924 EXPECT_EQ("", service.files[1].title);
925 EXPECT_EQ(local_path.value(), service.files[1].local_file_path.value());
926
927 EXPECT_EQ(HTTP_SUCCESS, results[0].error);
928 EXPECT_TRUE(results[0].resume_url.is_empty());
929 EXPECT_TRUE(results[0].file);
930
931 EXPECT_EQ(HTTP_SUCCESS, results[1].error);
932 EXPECT_TRUE(results[1].resume_url.is_empty());
933 EXPECT_TRUE(results[1].file);
934 }
935
TEST_F(DriveUploaderTest,BatchProcessingWithError)936 TEST_F(DriveUploaderTest, BatchProcessingWithError) {
937 // Prepare test target.
938 MockDriveServiceForBatchProcessing service;
939 DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get(),
940 mojo::NullRemote());
941
942 struct {
943 DriveApiErrorCode error;
944 GURL resume_url;
945 std::unique_ptr<FileResource> file;
946 UploadCompletionCallback callback() {
947 return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
948 }
949 } results[2];
950
951 uploader.StartBatchProcessing();
952 uploader.UploadNewFile("parent_resource_id",
953 base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
954 "title", kTestMimeType, UploadNewFileOptions(),
955 results[0].callback(),
956 google_apis::ProgressCallback());
957 uploader.UploadExistingFile(
958 "resource_id", base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
959 kTestMimeType, UploadExistingFileOptions(), results[1].callback(),
960 google_apis::ProgressCallback());
961 uploader.StopBatchProcessing();
962 base::RunLoop().RunUntilIdle();
963
964 EXPECT_EQ(0u, service.files.size());
965 EXPECT_TRUE(service.committed);
966
967 EXPECT_EQ(HTTP_NOT_FOUND, results[0].error);
968 EXPECT_TRUE(results[0].resume_url.is_empty());
969 EXPECT_FALSE(results[0].file);
970
971 EXPECT_EQ(HTTP_NOT_FOUND, results[1].error);
972 EXPECT_TRUE(results[1].resume_url.is_empty());
973 EXPECT_FALSE(results[1].file);
974 }
975 } // namespace drive
976