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