blob: f45c1cf28c3bd92c35a66e1185b15315a580486c [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/drive/drive_uploader.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "components/drive/service/dummy_drive_service.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/drive/test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
using google_apis::CancelCallback;
using google_apis::FileResource;
using google_apis::DriveApiErrorCode;
using google_apis::DRIVE_NO_CONNECTION;
using google_apis::DRIVE_OTHER_ERROR;
using google_apis::HTTP_CONFLICT;
using google_apis::HTTP_CREATED;
using google_apis::HTTP_NOT_FOUND;
using google_apis::HTTP_PRECONDITION;
using google_apis::HTTP_RESUME_INCOMPLETE;
using google_apis::HTTP_SUCCESS;
using google_apis::InitiateUploadCallback;
using google_apis::ProgressCallback;
using google_apis::UploadRangeResponse;
using google_apis::drive::UploadRangeCallback;
namespace test_util = google_apis::test_util;
namespace drive {
namespace {
const char kTestDummyMd5[] = "dummy_md5";
const char kTestDocumentTitle[] = "Hello world";
const char kTestInitiateUploadParentResourceId[] = "parent_resource_id";
const char kTestInitiateUploadResourceId[] = "resource_id";
const char kTestMimeType[] = "text/plain";
const char kTestUploadNewFileURL[] = "http://test/upload_location/new_file";
const char kTestUploadExistingFileURL[] =
"http://test/upload_location/existing_file";
const int64_t kUploadChunkSize = 1024 * 1024 * 1024;
const char kTestETag[] = "test_etag";
CancelCallback SendMultipartUploadResult(
DriveApiErrorCode response_code,
int64_t content_length,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) {
// Callback progress
if (!progress_callback.is_null()) {
// For the testing purpose, it always notifies the progress at the end of
// whole file uploading.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(progress_callback, content_length, content_length));
}
// MultipartUploadXXXFile is an asynchronous function, so don't callback
// directly.
std::unique_ptr<FileResource> entry;
entry = std::make_unique<FileResource>();
entry->set_md5_checksum(kTestDummyMd5);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, response_code, std::move(entry)));
return CancelCallback();
}
// Mock DriveService that verifies if the uploaded content matches the preset
// expectation.
class MockDriveServiceWithUploadExpectation : public DummyDriveService {
public:
// Sets up an expected upload content. InitiateUpload and ResumeUpload will
// verify that the specified data is correctly uploaded.
MockDriveServiceWithUploadExpectation(
const base::FilePath& expected_upload_file,
int64_t expected_content_length)
: expected_upload_file_(expected_upload_file),
expected_content_length_(expected_content_length),
received_bytes_(0),
resume_upload_call_count_(0),
multipart_upload_call_count_(0) {}
int64_t received_bytes() const { return received_bytes_; }
void set_received_bytes(int64_t received_bytes) {
received_bytes_ = received_bytes;
}
int64_t resume_upload_call_count() const { return resume_upload_call_count_; }
int64_t multipart_upload_call_count() const {
return multipart_upload_call_count_;
}
private:
// DriveServiceInterface overrides.
// Handles a request for obtaining an upload location URL.
CancelCallback 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) override {
EXPECT_EQ(kTestDocumentTitle, title);
EXPECT_EQ(kTestMimeType, content_type);
EXPECT_EQ(expected_content_length_, content_length);
EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
// Calls back the upload URL for subsequent ResumeUpload requests.
// InitiateUpload is an asynchronous function, so don't callback directly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
return CancelCallback();
}
CancelCallback InitiateUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const UploadExistingFileOptions& options,
const InitiateUploadCallback& callback) override {
EXPECT_EQ(kTestMimeType, content_type);
EXPECT_EQ(expected_content_length_, content_length);
EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
if (!options.etag.empty() && options.etag != kTestETag) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, HTTP_PRECONDITION, GURL()));
return CancelCallback();
}
// Calls back the upload URL for subsequent ResumeUpload requests.
// InitiateUpload is an asynchronous function, so don't callback directly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, HTTP_SUCCESS,
GURL(kTestUploadExistingFileURL)));
return CancelCallback();
}
// Handles a request for uploading a chunk of bytes.
CancelCallback 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,
const UploadRangeCallback& callback,
const ProgressCallback& progress_callback) override {
// The upload range should start from the current first unreceived byte.
EXPECT_EQ(received_bytes_, start_position);
EXPECT_EQ(expected_upload_file_, local_file_path);
// The upload data must be split into 512KB chunks.
const int64_t expected_chunk_end =
std::min(received_bytes_ + kUploadChunkSize, expected_content_length_);
EXPECT_EQ(expected_chunk_end, end_position);
// The upload URL returned by InitiateUpload() must be used.
EXPECT_TRUE(upload_location == kTestUploadNewFileURL ||
upload_location == kTestUploadExistingFileURL);
// Other parameters should be the exact values passed to DriveUploader.
EXPECT_EQ(expected_content_length_, content_length);
EXPECT_EQ(kTestMimeType, content_type);
// Update the internal status of the current upload session.
resume_upload_call_count_++;
received_bytes_ = end_position;
// Callback progress
if (!progress_callback.is_null()) {
// For the testing purpose, it always notifies the progress at the end of
// each chunk uploading.
int64_t chunk_size = end_position - start_position;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(progress_callback, chunk_size, chunk_size));
}
SendUploadRangeResponse(upload_location, callback);
return CancelCallback();
}
// Handles a request to fetch the current upload status.
CancelCallback GetUploadStatus(const GURL& upload_location,
int64_t content_length,
const UploadRangeCallback& callback) override {
EXPECT_EQ(expected_content_length_, content_length);
// The upload URL returned by InitiateUpload() must be used.
EXPECT_TRUE(upload_location == kTestUploadNewFileURL ||
upload_location == kTestUploadExistingFileURL);
SendUploadRangeResponse(upload_location, callback);
return CancelCallback();
}
// Runs |callback| with the current upload status.
void SendUploadRangeResponse(const GURL& upload_location,
const UploadRangeCallback& callback) {
// Callback with response.
UploadRangeResponse response;
std::unique_ptr<FileResource> entry;
if (received_bytes_ == expected_content_length_) {
DriveApiErrorCode response_code =
upload_location == kTestUploadNewFileURL ?
HTTP_CREATED : HTTP_SUCCESS;
response = UploadRangeResponse(response_code, -1, -1);
entry = std::make_unique<FileResource>();
entry->set_md5_checksum(kTestDummyMd5);
} else {
response = UploadRangeResponse(
HTTP_RESUME_INCOMPLETE, 0, received_bytes_);
}
// ResumeUpload is an asynchronous function, so don't callback directly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, response, std::move(entry)));
}
CancelCallback 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,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
EXPECT_EQ(kTestMimeType, content_type);
EXPECT_EQ(expected_content_length_, content_length);
EXPECT_EQ(kTestInitiateUploadParentResourceId, parent_resource_id);
EXPECT_EQ(kTestDocumentTitle, title);
EXPECT_EQ(expected_upload_file_, local_file_path);
received_bytes_ = content_length;
multipart_upload_call_count_++;
return SendMultipartUploadResult(HTTP_CREATED, content_length, callback,
progress_callback);
}
CancelCallback MultipartUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const base::FilePath& local_file_path,
const UploadExistingFileOptions& options,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
EXPECT_EQ(kTestMimeType, content_type);
EXPECT_EQ(expected_content_length_, content_length);
EXPECT_EQ(kTestInitiateUploadResourceId, resource_id);
EXPECT_EQ(expected_upload_file_, local_file_path);
if (!options.etag.empty() && options.etag != kTestETag) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, HTTP_PRECONDITION, nullptr));
return CancelCallback();
}
received_bytes_ = content_length;
multipart_upload_call_count_++;
return SendMultipartUploadResult(HTTP_SUCCESS, content_length, callback,
progress_callback);
}
const base::FilePath expected_upload_file_;
const int64_t expected_content_length_;
int64_t received_bytes_;
int64_t resume_upload_call_count_;
int64_t multipart_upload_call_count_;
};
// Mock DriveService that returns a failure at InitiateUpload().
class MockDriveServiceNoConnectionAtInitiate : public DummyDriveService {
// Returns error.
CancelCallback 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) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, GURL()));
return CancelCallback();
}
CancelCallback InitiateUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const UploadExistingFileOptions& options,
const InitiateUploadCallback& callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, GURL()));
return CancelCallback();
}
// Should not be used.
CancelCallback 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,
const UploadRangeCallback& callback,
const ProgressCallback& progress_callback) override {
NOTREACHED();
return CancelCallback();
}
CancelCallback 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,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, nullptr));
return CancelCallback();
}
CancelCallback MultipartUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const base::FilePath& local_file_path,
const UploadExistingFileOptions& options,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, DRIVE_NO_CONNECTION, nullptr));
return CancelCallback();
}
};
// Mock DriveService that returns a failure at ResumeUpload().
class MockDriveServiceNoConnectionAtResume : public DummyDriveService {
// Succeeds and returns an upload location URL.
CancelCallback 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) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(callback, HTTP_SUCCESS, GURL(kTestUploadNewFileURL)));
return CancelCallback();
}
CancelCallback InitiateUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const UploadExistingFileOptions& options,
const InitiateUploadCallback& callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, HTTP_SUCCESS,
GURL(kTestUploadExistingFileURL)));
return CancelCallback();
}
// Returns error.
CancelCallback 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,
const UploadRangeCallback& callback,
const ProgressCallback& progress_callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(callback,
UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
nullptr));
return CancelCallback();
}
};
// Mock DriveService that returns a failure at GetUploadStatus().
class MockDriveServiceNoConnectionAtGetUploadStatus : public DummyDriveService {
// Returns error.
CancelCallback GetUploadStatus(const GURL& upload_url,
int64_t content_length,
const UploadRangeCallback& callback) override {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(callback,
UploadRangeResponse(DRIVE_NO_CONNECTION, -1, -1),
nullptr));
return CancelCallback();
}
};
class DriveUploaderTest : public testing::Test {
public:
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
protected:
base::test::ScopedTaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
};
} // namespace
TEST_F(DriveUploaderTest, UploadExisting0KB) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(temp_dir_.GetPath(), 0,
&local_path, &data));
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
std::vector<test_util::ProgressInfo> upload_progress_values;
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
base::Bind(&test_util::AppendProgressCallbackResult,
&upload_progress_values));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, mock_service.resume_upload_call_count());
EXPECT_EQ(1, mock_service.multipart_upload_call_count());
EXPECT_EQ(0, mock_service.received_bytes());
EXPECT_EQ(HTTP_SUCCESS, error);
EXPECT_TRUE(upload_location.is_empty());
ASSERT_TRUE(entry);
EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
ASSERT_EQ(1U, upload_progress_values.size());
EXPECT_EQ(test_util::ProgressInfo(0, 0), upload_progress_values[0]);
}
TEST_F(DriveUploaderTest, UploadExisting512KB) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
std::vector<test_util::ProgressInfo> upload_progress_values;
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
base::Bind(&test_util::AppendProgressCallbackResult,
&upload_progress_values));
base::RunLoop().RunUntilIdle();
// 512KB upload should be uploaded as multipart body.
EXPECT_EQ(0, mock_service.resume_upload_call_count());
EXPECT_EQ(1, mock_service.multipart_upload_call_count());
EXPECT_EQ(512 * 1024, mock_service.received_bytes());
EXPECT_EQ(HTTP_SUCCESS, error);
EXPECT_TRUE(upload_location.is_empty());
ASSERT_TRUE(entry);
EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
ASSERT_EQ(1U, upload_progress_values.size());
EXPECT_EQ(test_util::ProgressInfo(512 * 1024, 512 * 1024),
upload_progress_values[0]);
}
TEST_F(DriveUploaderTest, UploadExisting2MB) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
std::vector<test_util::ProgressInfo> upload_progress_values;
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
base::Bind(&test_util::AppendProgressCallbackResult,
&upload_progress_values));
base::RunLoop().RunUntilIdle();
// 2MB upload should not be split into multiple chunks.
EXPECT_EQ(1, mock_service.resume_upload_call_count());
EXPECT_EQ(0, mock_service.multipart_upload_call_count());
EXPECT_EQ(2 * 1024 * 1024, mock_service.received_bytes());
EXPECT_EQ(HTTP_SUCCESS, error);
EXPECT_TRUE(upload_location.is_empty());
ASSERT_TRUE(entry);
EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
ASSERT_EQ(1U, upload_progress_values.size());
EXPECT_EQ(test_util::ProgressInfo(2 * 1024 * 1024, 2 * 1024 * 1024),
upload_progress_values[0]);
}
TEST_F(DriveUploaderTest, InitiateUploadFail) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
DriveApiErrorCode error = HTTP_SUCCESS;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceNoConnectionAtInitiate mock_service;
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DRIVE_NO_CONNECTION, error);
EXPECT_TRUE(upload_location.is_empty());
EXPECT_FALSE(entry);
}
TEST_F(DriveUploaderTest, MultipartUploadFail) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
DriveApiErrorCode error = HTTP_SUCCESS;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceNoConnectionAtInitiate mock_service;
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DRIVE_NO_CONNECTION, error);
EXPECT_TRUE(upload_location.is_empty());
EXPECT_FALSE(entry);
}
TEST_F(DriveUploaderTest, InitiateUploadNoConflict) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
UploadExistingFileOptions options;
options.etag = kTestETag;
uploader.UploadExistingFile(kTestInitiateUploadResourceId,
local_path,
kTestMimeType,
options,
test_util::CreateCopyResultCallback(
&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(HTTP_SUCCESS, error);
EXPECT_TRUE(upload_location.is_empty());
}
TEST_F(DriveUploaderTest, MultipartUploadConflict) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 512 * 1024, &local_path, &data));
const std::string kDestinationETag("destination_etag");
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
UploadExistingFileOptions options;
options.etag = kDestinationETag;
uploader.UploadExistingFile(kTestInitiateUploadResourceId,
local_path,
kTestMimeType,
options,
test_util::CreateCopyResultCallback(
&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(HTTP_CONFLICT, error);
EXPECT_TRUE(upload_location.is_empty());
}
TEST_F(DriveUploaderTest, InitiateUploadConflict) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
const std::string kDestinationETag("destination_etag");
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
UploadExistingFileOptions options;
options.etag = kDestinationETag;
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType, options,
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(HTTP_CONFLICT, error);
EXPECT_TRUE(upload_location.is_empty());
}
TEST_F(DriveUploaderTest, ResumeUploadFail) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
DriveApiErrorCode error = HTTP_SUCCESS;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceNoConnectionAtResume mock_service;
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
uploader.UploadExistingFile(
kTestInitiateUploadResourceId, local_path, kTestMimeType,
UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DRIVE_NO_CONNECTION, error);
EXPECT_EQ(GURL(kTestUploadExistingFileURL), upload_location);
}
TEST_F(DriveUploaderTest, GetUploadStatusFail) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 2 * 1024 * 1024, &local_path, &data));
DriveApiErrorCode error = HTTP_SUCCESS;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceNoConnectionAtGetUploadStatus mock_service;
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
uploader.ResumeUploadFile(GURL(kTestUploadExistingFileURL),
local_path,
kTestMimeType,
test_util::CreateCopyResultCallback(
&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(DRIVE_NO_CONNECTION, error);
EXPECT_TRUE(upload_location.is_empty());
}
TEST_F(DriveUploaderTest, NonExistingSourceFile) {
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
DriveUploader uploader(nullptr, // nullptr, the service won't be used.
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
uploader.UploadExistingFile(
kTestInitiateUploadResourceId,
temp_dir_.GetPath().AppendASCII("_this_path_should_not_exist_"),
kTestMimeType, UploadExistingFileOptions(),
test_util::CreateCopyResultCallback(&error, &upload_location, &entry),
google_apis::ProgressCallback());
base::RunLoop().RunUntilIdle();
// Should return failure without doing any attempt to connect to the server.
EXPECT_EQ(HTTP_NOT_FOUND, error);
EXPECT_TRUE(upload_location.is_empty());
}
TEST_F(DriveUploaderTest, ResumeUpload) {
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), 1024 * 1024, &local_path, &data));
DriveApiErrorCode error = DRIVE_OTHER_ERROR;
GURL upload_location;
std::unique_ptr<FileResource> entry;
MockDriveServiceWithUploadExpectation mock_service(local_path, data.size());
DriveUploader uploader(&mock_service,
base::ThreadTaskRunnerHandle::Get().get(), nullptr);
// Emulate the situation that the only first part is successfully uploaded,
// but not the latter half.
mock_service.set_received_bytes(512 * 1024);
std::vector<test_util::ProgressInfo> upload_progress_values;
uploader.ResumeUploadFile(
GURL(kTestUploadExistingFileURL),
local_path,
kTestMimeType,
test_util::CreateCopyResultCallback(
&error, &upload_location, &entry),
base::Bind(&test_util::AppendProgressCallbackResult,
&upload_progress_values));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, mock_service.resume_upload_call_count());
EXPECT_EQ(1024 * 1024, mock_service.received_bytes());
EXPECT_EQ(HTTP_SUCCESS, error);
EXPECT_TRUE(upload_location.is_empty());
ASSERT_TRUE(entry);
EXPECT_EQ(kTestDummyMd5, entry->md5_checksum());
ASSERT_EQ(1U, upload_progress_values.size());
EXPECT_EQ(test_util::ProgressInfo(1024 * 1024, 1024 * 1024),
upload_progress_values[0]);
}
class MockDriveServiceForBatchProcessing : public DummyDriveService {
public:
struct UploadFileInfo {
enum { NEW_FILE, EXISTING_FILE } type;
std::string content_type;
uint64_t content_length;
std::string parent_resource_id;
std::string resource_id;
std::string title;
base::FilePath local_file_path;
google_apis::FileResourceCallback callback;
google_apis::ProgressCallback progress_callback;
};
class BatchRequestConfigurator : public BatchRequestConfiguratorInterface {
public:
explicit BatchRequestConfigurator(
MockDriveServiceForBatchProcessing* service)
: service(service) {}
CancelCallback 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,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
UploadFileInfo info;
info.type = UploadFileInfo::NEW_FILE;
info.content_type = content_type;
info.content_length = content_length;
info.parent_resource_id = parent_resource_id;
info.title = title;
info.local_file_path = local_file_path;
info.callback = callback;
info.progress_callback = progress_callback;
service->files.push_back(info);
return CancelCallback();
}
CancelCallback MultipartUploadExistingFile(
const std::string& content_type,
int64_t content_length,
const std::string& resource_id,
const base::FilePath& local_file_path,
const UploadExistingFileOptions& options,
const google_apis::FileResourceCallback& callback,
const google_apis::ProgressCallback& progress_callback) override {
UploadFileInfo info;
info.type = UploadFileInfo::EXISTING_FILE;
info.content_type = content_type;
info.content_length = content_length;
info.resource_id = resource_id;
info.local_file_path = local_file_path;
info.callback = callback;
info.progress_callback = progress_callback;
service->files.push_back(info);
return CancelCallback();
}
void Commit() override {
ASSERT_FALSE(service->committed);
service->committed = true;
for (const auto& file : service->files) {
SendMultipartUploadResult(HTTP_SUCCESS, file.content_length,
file.callback, file.progress_callback);
}
}
private:
MockDriveServiceForBatchProcessing* service;
};
public:
std::unique_ptr<BatchRequestConfiguratorInterface> StartBatchRequest()
override {
committed = false;
return std::unique_ptr<BatchRequestConfiguratorInterface>(
new BatchRequestConfigurator(this));
}
std::vector<UploadFileInfo> files;
bool committed;
};
TEST_F(DriveUploaderTest, BatchProcessing) {
// Preapre test file.
const size_t kTestFileSize = 1024 * 512;
base::FilePath local_path;
std::string data;
ASSERT_TRUE(test_util::CreateFileOfSpecifiedSize(
temp_dir_.GetPath(), kTestFileSize, &local_path, &data));
// Prepare test target.
MockDriveServiceForBatchProcessing service;
DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get(),
nullptr);
struct {
DriveApiErrorCode error;
GURL resume_url;
std::unique_ptr<FileResource> file;
UploadCompletionCallback callback() {
return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
}
} results[2];
uploader.StartBatchProcessing();
uploader.UploadNewFile("parent_resource_id", local_path, "title",
kTestMimeType, UploadNewFileOptions(),
results[0].callback(),
google_apis::ProgressCallback());
uploader.UploadExistingFile(
"resource_id", local_path, kTestMimeType, UploadExistingFileOptions(),
results[1].callback(), google_apis::ProgressCallback());
uploader.StopBatchProcessing();
base::RunLoop().RunUntilIdle();
ASSERT_EQ(2u, service.files.size());
EXPECT_TRUE(service.committed);
EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::NEW_FILE,
service.files[0].type);
EXPECT_EQ(kTestMimeType, service.files[0].content_type);
EXPECT_EQ(kTestFileSize, service.files[0].content_length);
EXPECT_EQ("parent_resource_id", service.files[0].parent_resource_id);
EXPECT_EQ("", service.files[0].resource_id);
EXPECT_EQ("title", service.files[0].title);
EXPECT_EQ(local_path.value(), service.files[0].local_file_path.value());
EXPECT_EQ(MockDriveServiceForBatchProcessing::UploadFileInfo::EXISTING_FILE,
service.files[1].type);
EXPECT_EQ(kTestMimeType, service.files[1].content_type);
EXPECT_EQ(kTestFileSize, service.files[1].content_length);
EXPECT_EQ("", service.files[1].parent_resource_id);
EXPECT_EQ("resource_id", service.files[1].resource_id);
EXPECT_EQ("", service.files[1].title);
EXPECT_EQ(local_path.value(), service.files[1].local_file_path.value());
EXPECT_EQ(HTTP_SUCCESS, results[0].error);
EXPECT_TRUE(results[0].resume_url.is_empty());
EXPECT_TRUE(results[0].file);
EXPECT_EQ(HTTP_SUCCESS, results[1].error);
EXPECT_TRUE(results[1].resume_url.is_empty());
EXPECT_TRUE(results[1].file);
}
TEST_F(DriveUploaderTest, BatchProcessingWithError) {
// Prepare test target.
MockDriveServiceForBatchProcessing service;
DriveUploader uploader(&service, base::ThreadTaskRunnerHandle::Get().get(),
nullptr);
struct {
DriveApiErrorCode error;
GURL resume_url;
std::unique_ptr<FileResource> file;
UploadCompletionCallback callback() {
return test_util::CreateCopyResultCallback(&error, &resume_url, &file);
}
} results[2];
uploader.StartBatchProcessing();
uploader.UploadNewFile("parent_resource_id",
base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
"title", kTestMimeType, UploadNewFileOptions(),
results[0].callback(),
google_apis::ProgressCallback());
uploader.UploadExistingFile(
"resource_id", base::FilePath(FILE_PATH_LITERAL("/path/non_exists")),
kTestMimeType, UploadExistingFileOptions(), results[1].callback(),
google_apis::ProgressCallback());
uploader.StopBatchProcessing();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0u, service.files.size());
EXPECT_TRUE(service.committed);
EXPECT_EQ(HTTP_NOT_FOUND, results[0].error);
EXPECT_TRUE(results[0].resume_url.is_empty());
EXPECT_FALSE(results[0].file);
EXPECT_EQ(HTTP_NOT_FOUND, results[1].error);
EXPECT_TRUE(results[1].resume_url.is_empty());
EXPECT_FALSE(results[1].file);
}
} // namespace drive