diff options
Diffstat (limited to 'components/drive/service')
-rw-r--r-- | components/drive/service/drive_api_service.cc | 872 | ||||
-rw-r--r-- | components/drive/service/drive_api_service.h | 269 | ||||
-rw-r--r-- | components/drive/service/drive_api_service_unittest.cc | 79 | ||||
-rw-r--r-- | components/drive/service/drive_service_interface.cc | 27 | ||||
-rw-r--r-- | components/drive/service/drive_service_interface.h | 467 | ||||
-rw-r--r-- | components/drive/service/dummy_drive_service.cc | 224 | ||||
-rw-r--r-- | components/drive/service/dummy_drive_service.h | 166 | ||||
-rw-r--r-- | components/drive/service/fake_drive_service.cc | 1787 | ||||
-rw-r--r-- | components/drive/service/fake_drive_service.h | 406 | ||||
-rw-r--r-- | components/drive/service/fake_drive_service_unittest.cc | 2177 | ||||
-rw-r--r-- | components/drive/service/test_util.cc | 193 | ||||
-rw-r--r-- | components/drive/service/test_util.h | 19 |
12 files changed, 6686 insertions, 0 deletions
diff --git a/components/drive/service/drive_api_service.cc b/components/drive/service/drive_api_service.cc new file mode 100644 index 0000000..644079a --- /dev/null +++ b/components/drive/service/drive_api_service.cc @@ -0,0 +1,872 @@ +// 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/service/drive_api_service.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/strings/stringprintf.h" +#include "components/drive/drive_api_util.h" +#include "google_apis/drive/auth_service.h" +#include "google_apis/drive/base_requests.h" +#include "google_apis/drive/drive_api_parser.h" +#include "google_apis/drive/drive_api_requests.h" +#include "google_apis/drive/files_list_request_runner.h" +#include "google_apis/drive/request_sender.h" +#include "google_apis/google_api_keys.h" +#include "net/url_request/url_request_context_getter.h" + +using google_apis::AboutResourceCallback; +using google_apis::AppList; +using google_apis::AppListCallback; +using google_apis::AuthStatusCallback; +using google_apis::AuthorizeAppCallback; +using google_apis::CancelCallback; +using google_apis::ChangeList; +using google_apis::ChangeListCallback; +using google_apis::DownloadActionCallback; +using google_apis::EntryActionCallback; +using google_apis::FileList; +using google_apis::FileListCallback; +using google_apis::FileResource; +using google_apis::FileResourceCallback; +using google_apis::DRIVE_OTHER_ERROR; +using google_apis::DRIVE_PARSE_ERROR; +using google_apis::DriveApiErrorCode; +using google_apis::GetContentCallback; +using google_apis::GetShareUrlCallback; +using google_apis::HTTP_NOT_IMPLEMENTED; +using google_apis::HTTP_SUCCESS; +using google_apis::InitiateUploadCallback; +using google_apis::ProgressCallback; +using google_apis::RequestSender; +using google_apis::FilesListRequestRunner; +using google_apis::UploadRangeResponse; +using google_apis::drive::AboutGetRequest; +using google_apis::drive::AppsListRequest; +using google_apis::drive::ChangesListRequest; +using google_apis::drive::ChangesListNextPageRequest; +using google_apis::drive::ChildrenDeleteRequest; +using google_apis::drive::ChildrenInsertRequest; +using google_apis::drive::DownloadFileRequest; +using google_apis::drive::FilesCopyRequest; +using google_apis::drive::FilesGetRequest; +using google_apis::drive::FilesInsertRequest; +using google_apis::drive::FilesPatchRequest; +using google_apis::drive::FilesListRequest; +using google_apis::drive::FilesListNextPageRequest; +using google_apis::drive::FilesDeleteRequest; +using google_apis::drive::FilesTrashRequest; +using google_apis::drive::GetUploadStatusRequest; +using google_apis::drive::InitiateUploadExistingFileRequest; +using google_apis::drive::InitiateUploadNewFileRequest; +using google_apis::drive::ResumeUploadRequest; +using google_apis::drive::UploadRangeCallback; + +namespace drive { + +namespace { + +// OAuth2 scopes for Drive API. +const char kDriveScope[] = "https://www.googleapis.com/auth/drive"; +const char kDriveAppsReadonlyScope[] = + "https://www.googleapis.com/auth/drive.apps.readonly"; +const char kDriveAppsScope[] = "https://www.googleapis.com/auth/drive.apps"; +const char kDocsListScope[] = "https://docs.google.com/feeds/"; + +// Mime type to create a directory. +const char kFolderMimeType[] = "application/vnd.google-apps.folder"; + +// Max number of file entries to be fetched in a single http request. +// +// The larger the number is, +// - The total running time to fetch the whole file list will become shorter. +// - The running time for a single request tends to become longer. +// Since the file list fetching is a completely background task, for our side, +// only the total time matters. However, the server seems to have a time limit +// per single request, which disables us to set the largest value (1000). +// TODO(kinaba): make it larger when the server gets faster. +const int kMaxNumFilesResourcePerRequest = 300; +const int kMaxNumFilesResourcePerRequestForSearch = 100; + +// For performance, we declare all fields we use. +const char kAboutResourceFields[] = + "kind,quotaBytesTotal,quotaBytesUsedAggregate,largestChangeId,rootFolderId"; +const char kFileResourceFields[] = + "kind,id,title,createdDate,sharedWithMeDate,mimeType," + "md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width," + "imageMediaMetadata/height,imageMediaMetadata/rotation,etag," + "parents(id,parentLink),alternateLink," + "modifiedDate,lastViewedByMeDate,shared"; +const char kFileResourceOpenWithLinksFields[] = + "kind,id,openWithLinks/*"; +const char kFileResourceShareLinkFields[] = + "kind,id,shareLink"; +const char kFileListFields[] = + "kind,items(kind,id,title,createdDate,sharedWithMeDate," + "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width," + "imageMediaMetadata/height,imageMediaMetadata/rotation,etag," + "parents(id,parentLink),alternateLink," + "modifiedDate,lastViewedByMeDate,shared),nextLink"; +const char kChangeListFields[] = + "kind,items(file(kind,id,title,createdDate,sharedWithMeDate," + "mimeType,md5Checksum,fileSize,labels/trashed,imageMediaMetadata/width," + "imageMediaMetadata/height,imageMediaMetadata/rotation,etag," + "parents(id,parentLink),alternateLink,modifiedDate," + "lastViewedByMeDate,shared),deleted,id,fileId,modificationDate),nextLink," + "largestChangeId"; + +void ExtractOpenUrlAndRun(const std::string& app_id, + const AuthorizeAppCallback& callback, + DriveApiErrorCode error, + scoped_ptr<FileResource> value) { + DCHECK(!callback.is_null()); + + if (!value) { + callback.Run(error, GURL()); + return; + } + + const std::vector<FileResource::OpenWithLink>& open_with_links = + value->open_with_links(); + for (size_t i = 0; i < open_with_links.size(); ++i) { + if (open_with_links[i].app_id == app_id) { + callback.Run(HTTP_SUCCESS, open_with_links[i].open_url); + return; + } + } + + // Not found. + callback.Run(DRIVE_OTHER_ERROR, GURL()); +} + +void ExtractShareUrlAndRun(const GetShareUrlCallback& callback, + DriveApiErrorCode error, + scoped_ptr<FileResource> value) { + callback.Run(error, value ? value->share_link() : GURL()); +} + +// Ignores the |entry|, and runs the |callback|. +void EntryActionCallbackAdapter( + const EntryActionCallback& callback, + DriveApiErrorCode error, scoped_ptr<FileResource> entry) { + callback.Run(error); +} + +// The resource ID for the root directory for Drive API is defined in the spec: +// https://developers.google.com/drive/folder +const char kDriveApiRootDirectoryResourceId[] = "root"; + +} // namespace + +BatchRequestConfigurator::BatchRequestConfigurator( + const base::WeakPtr<google_apis::drive::BatchUploadRequest>& batch_request, + base::SequencedTaskRunner* task_runner, + const google_apis::DriveApiUrlGenerator& url_generator, + const google_apis::CancelCallback& cancel_callback) + : batch_request_(batch_request), + task_runner_(task_runner), + url_generator_(url_generator), + cancel_callback_(cancel_callback) { +} + +BatchRequestConfigurator::~BatchRequestConfigurator() { + // The batch requst has not been committed. + if (batch_request_) + cancel_callback_.Run(); +} + +google_apis::CancelCallback BatchRequestConfigurator::MultipartUploadNewFile( + const std::string& content_type, + int64 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) { + DCHECK(CalledOnValidThread()); + DCHECK(!callback.is_null()); + + scoped_ptr<google_apis::BatchableDelegate> delegate( + new google_apis::drive::MultipartUploadNewFileDelegate( + task_runner_.get(), title, parent_resource_id, content_type, + content_length, options.modified_date, options.last_viewed_by_me_date, + local_file_path, options.properties, url_generator_, callback, + progress_callback)); + // Batch request can be null when pre-authorization for the requst is failed + // in request sender. + if (batch_request_) + batch_request_->AddRequest(delegate.release()); + else + delegate->NotifyError(DRIVE_OTHER_ERROR); + return cancel_callback_; +} + +google_apis::CancelCallback +BatchRequestConfigurator::MultipartUploadExistingFile( + const std::string& content_type, + int64 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) { + DCHECK(CalledOnValidThread()); + DCHECK(!callback.is_null()); + + scoped_ptr<google_apis::BatchableDelegate> delegate( + new google_apis::drive::MultipartUploadExistingFileDelegate( + task_runner_.get(), options.title, resource_id, + options.parent_resource_id, content_type, content_length, + options.modified_date, options.last_viewed_by_me_date, + local_file_path, options.etag, options.properties, url_generator_, + callback, progress_callback)); + // Batch request can be null when pre-authorization for the requst is failed + // in request sender. + if (batch_request_) + batch_request_->AddRequest(delegate.release()); + else + delegate->NotifyError(DRIVE_OTHER_ERROR); + return cancel_callback_; +} + +void BatchRequestConfigurator::Commit() { + DCHECK(CalledOnValidThread()); + if (!batch_request_) + return; + batch_request_->Commit(); + batch_request_.reset(); +} + +DriveAPIService::DriveAPIService( + OAuth2TokenService* oauth2_token_service, + net::URLRequestContextGetter* url_request_context_getter, + base::SequencedTaskRunner* blocking_task_runner, + const GURL& base_url, + const GURL& base_download_url, + const std::string& custom_user_agent) + : oauth2_token_service_(oauth2_token_service), + url_request_context_getter_(url_request_context_getter), + blocking_task_runner_(blocking_task_runner), + url_generator_(base_url, base_download_url), + custom_user_agent_(custom_user_agent) { +} + +DriveAPIService::~DriveAPIService() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (sender_.get()) + sender_->auth_service()->RemoveObserver(this); +} + +void DriveAPIService::Initialize(const std::string& account_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + + std::vector<std::string> scopes; + scopes.push_back(kDriveScope); + scopes.push_back(kDriveAppsReadonlyScope); + scopes.push_back(kDriveAppsScope); + + // Note: The following scope is used to support GetShareUrl on Drive API v2. + // Unfortunately, there is no support on Drive API v2, so we need to fall back + // to GData WAPI for the GetShareUrl. + scopes.push_back(kDocsListScope); + + sender_.reset(new RequestSender( + new google_apis::AuthService(oauth2_token_service_, + account_id, + url_request_context_getter_.get(), + scopes), + url_request_context_getter_.get(), + blocking_task_runner_.get(), + custom_user_agent_)); + sender_->auth_service()->AddObserver(this); + + files_list_request_runner_.reset( + new FilesListRequestRunner(sender_.get(), url_generator_)); +} + +void DriveAPIService::AddObserver(DriveServiceObserver* observer) { + observers_.AddObserver(observer); +} + +void DriveAPIService::RemoveObserver(DriveServiceObserver* observer) { + observers_.RemoveObserver(observer); +} + +bool DriveAPIService::CanSendRequest() const { + DCHECK(thread_checker_.CalledOnValidThread()); + + return HasRefreshToken(); +} + +std::string DriveAPIService::GetRootResourceId() const { + return kDriveApiRootDirectoryResourceId; +} + +CancelCallback DriveAPIService::GetAllFileList( + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesListRequest* request = new FilesListRequest( + sender_.get(), url_generator_, callback); + request->set_max_results(kMaxNumFilesResourcePerRequest); + request->set_q("trashed = false"); // Exclude trashed files. + request->set_fields(kFileListFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetFileListInDirectory( + const std::string& directory_resource_id, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!directory_resource_id.empty()); + DCHECK(!callback.is_null()); + + // Because children.list method on Drive API v2 returns only the list of + // children's references, but we need all file resource list. + // So, here we use files.list method instead, with setting parents query. + // After the migration from GData WAPI to Drive API v2, we should clean the + // code up by moving the responsibility to include "parents" in the query + // to client side. + // We aren't interested in files in trash in this context, neither. + return files_list_request_runner_->CreateAndStartWithSizeBackoff( + kMaxNumFilesResourcePerRequest, + base::StringPrintf( + "'%s' in parents and trashed = false", + util::EscapeQueryStringValue(directory_resource_id).c_str()), + kFileListFields, callback); +} + +CancelCallback DriveAPIService::Search( + const std::string& search_query, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!search_query.empty()); + DCHECK(!callback.is_null()); + + return files_list_request_runner_->CreateAndStartWithSizeBackoff( + kMaxNumFilesResourcePerRequestForSearch, + util::TranslateQuery(search_query), kFileListFields, callback); +} + +CancelCallback DriveAPIService::SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!title.empty()); + DCHECK(!callback.is_null()); + + std::string query; + base::StringAppendF(&query, "title = '%s'", + util::EscapeQueryStringValue(title).c_str()); + if (!directory_resource_id.empty()) { + base::StringAppendF( + &query, " and '%s' in parents", + util::EscapeQueryStringValue(directory_resource_id).c_str()); + } + query += " and trashed = false"; + + FilesListRequest* request = new FilesListRequest( + sender_.get(), url_generator_, callback); + request->set_max_results(kMaxNumFilesResourcePerRequest); + request->set_q(query); + request->set_fields(kFileListFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetChangeList( + int64 start_changestamp, + const ChangeListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + ChangesListRequest* request = new ChangesListRequest( + sender_.get(), url_generator_, callback); + request->set_max_results(kMaxNumFilesResourcePerRequest); + request->set_start_change_id(start_changestamp); + request->set_fields(kChangeListFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetRemainingChangeList( + const GURL& next_link, + const ChangeListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!next_link.is_empty()); + DCHECK(!callback.is_null()); + + ChangesListNextPageRequest* request = new ChangesListNextPageRequest( + sender_.get(), callback); + request->set_next_link(next_link); + request->set_fields(kChangeListFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetRemainingFileList( + const GURL& next_link, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!next_link.is_empty()); + DCHECK(!callback.is_null()); + + FilesListNextPageRequest* request = new FilesListNextPageRequest( + sender_.get(), callback); + request->set_next_link(next_link); + request->set_fields(kFileListFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetFileResource( + const std::string& resource_id, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesGetRequest* request = new FilesGetRequest( + sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(), + callback); + request->set_file_id(resource_id); + request->set_fields(kFileResourceFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const GetShareUrlCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (!google_apis::IsGoogleChromeAPIKeyUsed()) { + LOG(ERROR) << "Only the official build of Chrome OS can open share dialogs " + << "from the file manager."; + } + + FilesGetRequest* request = new FilesGetRequest( + sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(), + base::Bind(&ExtractShareUrlAndRun, callback)); + request->set_file_id(resource_id); + request->set_fields(kFileResourceShareLinkFields); + request->set_embed_origin(embed_origin); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetAboutResource( + const AboutResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + AboutGetRequest* request = + new AboutGetRequest(sender_.get(), url_generator_, callback); + request->set_fields(kAboutResourceFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::GetAppList(const AppListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + return sender_->StartRequestWithAuthRetry( + new AppsListRequest(sender_.get(), url_generator_, + google_apis::IsGoogleChromeAPIKeyUsed(), callback)); +} + +CancelCallback DriveAPIService::DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const DownloadActionCallback& download_action_callback, + const GetContentCallback& get_content_callback, + const ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!download_action_callback.is_null()); + // get_content_callback may be null. + + return sender_->StartRequestWithAuthRetry(new DownloadFileRequest( + sender_.get(), url_generator_, resource_id, local_cache_path, + download_action_callback, get_content_callback, progress_callback)); +} + +CancelCallback DriveAPIService::DeleteResource( + const std::string& resource_id, + const std::string& etag, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesDeleteRequest* request = new FilesDeleteRequest( + sender_.get(), url_generator_, callback); + request->set_file_id(resource_id); + request->set_etag(etag); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::TrashResource( + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesTrashRequest* request = new FilesTrashRequest( + sender_.get(), url_generator_, + base::Bind(&EntryActionCallbackAdapter, callback)); + request->set_file_id(resource_id); + request->set_fields(kFileResourceFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesInsertRequest* request = new FilesInsertRequest( + sender_.get(), url_generator_, callback); + request->set_last_viewed_by_me_date(options.last_viewed_by_me_date); + request->set_mime_type(kFolderMimeType); + request->set_modified_date(options.modified_date); + request->add_parent(parent_resource_id); + request->set_title(directory_title); + request->set_properties(options.properties); + request->set_fields(kFileResourceFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesCopyRequest* request = new FilesCopyRequest( + sender_.get(), url_generator_, callback); + request->set_file_id(resource_id); + request->add_parent(parent_resource_id); + request->set_title(new_title); + request->set_modified_date(last_modified); + request->set_fields(kFileResourceFields); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FilesPatchRequest* request = new FilesPatchRequest( + sender_.get(), url_generator_, callback); + request->set_file_id(resource_id); + request->set_title(new_title); + if (!parent_resource_id.empty()) + request->add_parent(parent_resource_id); + if (!last_modified.is_null()) { + // Need to set setModifiedDate to true to overwrite modifiedDate. + request->set_set_modified_date(true); + request->set_modified_date(last_modified); + } + if (!last_viewed_by_me.is_null()) { + // Need to set updateViewedDate to false, otherwise the lastViewedByMeDate + // will be set to the request time (not the specified time via request). + request->set_update_viewed_date(false); + request->set_last_viewed_by_me_date(last_viewed_by_me); + } + request->set_fields(kFileResourceFields); + request->set_properties(properties); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + ChildrenInsertRequest* request = + new ChildrenInsertRequest(sender_.get(), url_generator_, callback); + request->set_folder_id(parent_resource_id); + request->set_id(resource_id); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + ChildrenDeleteRequest* request = + new ChildrenDeleteRequest(sender_.get(), url_generator_, callback); + request->set_child_id(resource_id); + request->set_folder_id(parent_resource_id); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const InitiateUploadCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + InitiateUploadNewFileRequest* request = + new InitiateUploadNewFileRequest(sender_.get(), + url_generator_, + content_type, + content_length, + parent_resource_id, + title, + callback); + request->set_modified_date(options.modified_date); + request->set_last_viewed_by_me_date(options.last_viewed_by_me_date); + request->set_properties(options.properties); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const InitiateUploadCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + InitiateUploadExistingFileRequest* request = + new InitiateUploadExistingFileRequest(sender_.get(), + url_generator_, + content_type, + content_length, + resource_id, + options.etag, + callback); + request->set_parent_resource_id(options.parent_resource_id); + request->set_title(options.title); + request->set_modified_date(options.modified_date); + request->set_last_viewed_by_me_date(options.last_viewed_by_me_date); + request->set_properties(options.properties); + return sender_->StartRequestWithAuthRetry(request); +} + +CancelCallback DriveAPIService::ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const UploadRangeCallback& callback, + const ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + return sender_->StartRequestWithAuthRetry(new ResumeUploadRequest( + sender_.get(), upload_url, start_position, end_position, content_length, + content_type, local_file_path, callback, progress_callback)); +} + +CancelCallback DriveAPIService::GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const UploadRangeCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + return sender_->StartRequestWithAuthRetry(new GetUploadStatusRequest( + sender_.get(), upload_url, content_length, callback)); +} + +CancelCallback DriveAPIService::MultipartUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const base::FilePath& local_file_path, + const drive::UploadNewFileOptions& options, + const FileResourceCallback& callback, + const google_apis::ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + return sender_->StartRequestWithAuthRetry( + new google_apis::drive::SingleBatchableDelegateRequest( + sender_.get(), + new google_apis::drive::MultipartUploadNewFileDelegate( + sender_->blocking_task_runner(), title, parent_resource_id, + content_type, content_length, options.modified_date, + options.last_viewed_by_me_date, local_file_path, + options.properties, url_generator_, callback, + progress_callback))); +} + +CancelCallback DriveAPIService::MultipartUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const base::FilePath& local_file_path, + const drive::UploadExistingFileOptions& options, + const FileResourceCallback& callback, + const google_apis::ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + return sender_->StartRequestWithAuthRetry( + new google_apis::drive::SingleBatchableDelegateRequest( + sender_.get(), + new google_apis::drive::MultipartUploadExistingFileDelegate( + sender_->blocking_task_runner(), options.title, resource_id, + options.parent_resource_id, content_type, content_length, + options.modified_date, options.last_viewed_by_me_date, + local_file_path, options.etag, options.properties, url_generator_, + callback, progress_callback))); +} + +CancelCallback DriveAPIService::AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const AuthorizeAppCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + // Files.Authorize is only available for whitelisted clients like official + // Google Chrome. In other cases, we fall back to Files.Get that returns the + // same value as Files.Authorize without doing authorization. In that case, + // the app can open if it was authorized by other means (from whitelisted + // clients or drive.google.com web UI.) + if (google_apis::IsGoogleChromeAPIKeyUsed()) { + google_apis::drive::FilesAuthorizeRequest* request = + new google_apis::drive::FilesAuthorizeRequest( + sender_.get(), url_generator_, + base::Bind(&ExtractOpenUrlAndRun, app_id, callback)); + request->set_app_id(app_id); + request->set_file_id(resource_id); + request->set_fields(kFileResourceOpenWithLinksFields); + return sender_->StartRequestWithAuthRetry(request); + } else { + FilesGetRequest* request = new FilesGetRequest( + sender_.get(), url_generator_, google_apis::IsGoogleChromeAPIKeyUsed(), + base::Bind(&ExtractOpenUrlAndRun, app_id, callback)); + request->set_file_id(resource_id); + request->set_fields(kFileResourceOpenWithLinksFields); + return sender_->StartRequestWithAuthRetry(request); + } +} + +CancelCallback DriveAPIService::UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + google_apis::drive::AppsDeleteRequest* request = + new google_apis::drive::AppsDeleteRequest(sender_.get(), url_generator_, + callback); + request->set_app_id(app_id); + return sender_->StartRequestWithAuthRetry(request); +} + +google_apis::CancelCallback DriveAPIService::AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + google_apis::drive::PermissionsInsertRequest* request = + new google_apis::drive::PermissionsInsertRequest(sender_.get(), + url_generator_, + callback); + request->set_id(resource_id); + request->set_role(role); + request->set_type(google_apis::drive::PERMISSION_TYPE_USER); + request->set_value(email); + return sender_->StartRequestWithAuthRetry(request); +} + +bool DriveAPIService::HasAccessToken() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return sender_->auth_service()->HasAccessToken(); +} + +void DriveAPIService::RequestAccessToken(const AuthStatusCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + const std::string access_token = sender_->auth_service()->access_token(); + if (!access_token.empty()) { + callback.Run(google_apis::HTTP_NOT_MODIFIED, access_token); + return; + } + + // Retrieve the new auth token. + sender_->auth_service()->StartAuthentication(callback); +} + +bool DriveAPIService::HasRefreshToken() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return sender_->auth_service()->HasRefreshToken(); +} + +void DriveAPIService::ClearAccessToken() { + DCHECK(thread_checker_.CalledOnValidThread()); + sender_->auth_service()->ClearAccessToken(); +} + +void DriveAPIService::ClearRefreshToken() { + DCHECK(thread_checker_.CalledOnValidThread()); + sender_->auth_service()->ClearRefreshToken(); +} + +void DriveAPIService::OnOAuth2RefreshTokenChanged() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (CanSendRequest()) { + FOR_EACH_OBSERVER( + DriveServiceObserver, observers_, OnReadyToSendRequests()); + } else if (!HasRefreshToken()) { + FOR_EACH_OBSERVER( + DriveServiceObserver, observers_, OnRefreshTokenInvalid()); + } +} + +scoped_ptr<BatchRequestConfiguratorInterface> +DriveAPIService::StartBatchRequest() { + scoped_ptr<google_apis::drive::BatchUploadRequest> request( + new google_apis::drive::BatchUploadRequest(sender_.get(), + url_generator_)); + const base::WeakPtr<google_apis::drive::BatchUploadRequest> weak_ref = + request->GetWeakPtrAsBatchUploadRequest(); + // Have sender_ manage the lifetime of the request. + // TODO(hirono): Currently we need to pass the ownership of the request to + // RequestSender before the request is committed because the request has a + // reference to RequestSender and we should ensure to delete the request when + // the sender is deleted. Resolve the circulating dependency and fix it. + const google_apis::CancelCallback callback = + sender_->StartRequestWithAuthRetry(request.release()); + return make_scoped_ptr<BatchRequestConfiguratorInterface>( + new BatchRequestConfigurator(weak_ref, sender_->blocking_task_runner(), + url_generator_, callback)); +} + +} // namespace drive diff --git a/components/drive/service/drive_api_service.h b/components/drive/service/drive_api_service.h new file mode 100644 index 0000000..22bb7d4 --- /dev/null +++ b/components/drive/service/drive_api_service.h @@ -0,0 +1,269 @@ +// 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. + +#ifndef COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_ +#define COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "components/drive/service/drive_service_interface.h" +#include "google_apis/drive/auth_service_interface.h" +#include "google_apis/drive/auth_service_observer.h" +#include "google_apis/drive/drive_api_url_generator.h" + +class GURL; +class OAuth2TokenService; + +namespace base { +class FilePath; +class SequencedTaskRunner; +} + +namespace google_apis { +class FilesListRequestRunner; +class RequestSender; +namespace drive { +class BatchUploadRequest; +} // namespace drive +} // namespace google_apis + +namespace net { +class URLRequestContextGetter; +} // namespace net + +namespace drive { + +// Builder for batch request returned by |DriveAPIService|. +class BatchRequestConfigurator : public BatchRequestConfiguratorInterface, + public base::NonThreadSafe { + public: + BatchRequestConfigurator( + const base::WeakPtr<google_apis::drive::BatchUploadRequest>& + batch_request, + base::SequencedTaskRunner* task_runner, + const google_apis::DriveApiUrlGenerator& url_generator, + const google_apis::CancelCallback& cancel_callback); + ~BatchRequestConfigurator() override; + + // BatchRequestConfiguratorInterface overrides. + google_apis::CancelCallback MultipartUploadNewFile( + const std::string& content_type, + int64 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; + google_apis::CancelCallback MultipartUploadExistingFile( + const std::string& content_type, + int64 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; + void Commit() override; + + private: + // Reference to batch request. It turns to null after committing. + base::WeakPtr<google_apis::drive::BatchUploadRequest> batch_request_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + google_apis::DriveApiUrlGenerator url_generator_; + google_apis::CancelCallback cancel_callback_; + + DISALLOW_COPY_AND_ASSIGN(BatchRequestConfigurator); +}; + +// This class provides Drive request calls using Drive V2 API. +// Details of API call are abstracted in each request class and this class +// works as a thin wrapper for the API. +class DriveAPIService : public DriveServiceInterface, + public google_apis::AuthServiceObserver { + public: + // |oauth2_token_service| is used for obtaining OAuth2 access tokens. + // |url_request_context_getter| is used to initialize URLFetcher. + // |blocking_task_runner| is used to run blocking tasks (like parsing JSON). + // |base_url| is used to generate URLs for communication with the drive API. + // |base_download_url| is used to generate URLs for downloading file from the + // drive API. + // |custom_user_agent| will be used for the User-Agent header in HTTP + // requests issues through the service if the value is not empty. + DriveAPIService( + OAuth2TokenService* oauth2_token_service, + net::URLRequestContextGetter* url_request_context_getter, + base::SequencedTaskRunner* blocking_task_runner, + const GURL& base_url, + const GURL& base_download_url, + const std::string& custom_user_agent); + ~DriveAPIService() override; + + // DriveServiceInterface Overrides + void Initialize(const std::string& account_id) override; + void AddObserver(DriveServiceObserver* observer) override; + void RemoveObserver(DriveServiceObserver* observer) override; + bool CanSendRequest() const override; + bool HasAccessToken() const override; + void RequestAccessToken( + const google_apis::AuthStatusCallback& callback) override; + bool HasRefreshToken() const override; + void ClearAccessToken() override; + void ClearRefreshToken() override; + std::string GetRootResourceId() const override; + google_apis::CancelCallback GetAllFileList( + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileListInDirectory( + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback Search( + const std::string& search_query, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetChangeList( + int64 start_changestamp, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingChangeList( + const GURL& next_link, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingFileList( + const GURL& next_link, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileResource( + const std::string& resource_id, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const google_apis::GetShareUrlCallback& callback) override; + google_apis::CancelCallback GetAboutResource( + const google_apis::AboutResourceCallback& callback) override; + google_apis::CancelCallback GetAppList( + const google_apis::AppListCallback& callback) override; + google_apis::CancelCallback DeleteResource( + const std::string& resource_id, + const std::string& etag, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback TrashResource( + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const google_apis::DownloadActionCallback& download_action_callback, + const google_apis::GetContentCallback& get_content_callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const google_apis::drive::UploadRangeCallback& callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const google_apis::drive::UploadRangeCallback& callback) override; + google_apis::CancelCallback MultipartUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const base::FilePath& local_file_path, + const drive::UploadNewFileOptions& options, + const google_apis::FileResourceCallback& callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback MultipartUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const base::FilePath& local_file_path, + const drive::UploadExistingFileOptions& options, + const google_apis::FileResourceCallback& callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const google_apis::AuthorizeAppCallback& callback) override; + google_apis::CancelCallback UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) override; + scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override; + + private: + // AuthServiceObserver override. + void OnOAuth2RefreshTokenChanged() override; + + // The class is expected to run on UI thread. + base::ThreadChecker thread_checker_; + + OAuth2TokenService* oauth2_token_service_; + scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; + scoped_ptr<google_apis::RequestSender> sender_; + scoped_ptr<google_apis::FilesListRequestRunner> files_list_request_runner_; + base::ObserverList<DriveServiceObserver> observers_; + google_apis::DriveApiUrlGenerator url_generator_; + const std::string custom_user_agent_; + + DISALLOW_COPY_AND_ASSIGN(DriveAPIService); +}; + +} // namespace drive + +#endif // COMPONENTS_DRIVE_SERVICE_DRIVE_API_SERVICE_H_ diff --git a/components/drive/service/drive_api_service_unittest.cc b/components/drive/service/drive_api_service_unittest.cc new file mode 100644 index 0000000..6655e68 --- /dev/null +++ b/components/drive/service/drive_api_service_unittest.cc @@ -0,0 +1,79 @@ +// Copyright 2015 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 "base/message_loop/message_loop.h" +#include "base/test/test_simple_task_runner.h" +#include "components/drive/service/drive_api_service.h" +#include "google_apis/drive/dummy_auth_service.h" +#include "google_apis/drive/request_sender.h" +#include "google_apis/drive/test_util.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace drive { +namespace { +const char kTestUserAgent[] = "test-user-agent"; +} + +class TestAuthService : public google_apis::DummyAuthService { + public: + void StartAuthentication( + const google_apis::AuthStatusCallback& callback) override { + callback_ = callback; + } + + bool HasAccessToken() const override { return false; } + + void SendHttpError() { + ASSERT_FALSE(callback_.is_null()); + callback_.Run(google_apis::HTTP_UNAUTHORIZED, ""); + } + + private: + google_apis::AuthStatusCallback callback_; +}; + +TEST(DriveAPIServiceTest, BatchRequestConfiguratorWithAuthFailure) { + const GURL test_base_url("http://localhost/"); + google_apis::DriveApiUrlGenerator url_generator(test_base_url, test_base_url); + scoped_refptr<base::TestSimpleTaskRunner> task_runner = + new base::TestSimpleTaskRunner(); + scoped_refptr<net::TestURLRequestContextGetter> request_context_getter = + new net::TestURLRequestContextGetter(task_runner.get()); + google_apis::RequestSender sender(new TestAuthService, + request_context_getter.get(), + task_runner.get(), kTestUserAgent); + google_apis::drive::BatchUploadRequest* const request = + new google_apis::drive::BatchUploadRequest(&sender, url_generator); + sender.StartRequestWithAuthRetry(request); + BatchRequestConfigurator configurator( + request->GetWeakPtrAsBatchUploadRequest(), task_runner.get(), + url_generator, google_apis::CancelCallback()); + static_cast<TestAuthService*>(sender.auth_service())->SendHttpError(); + + { + google_apis::DriveApiErrorCode error = google_apis::HTTP_SUCCESS; + scoped_ptr<google_apis::FileResource> file_resource; + configurator.MultipartUploadNewFile( + "text/plain", 10, "", "title", + base::FilePath(FILE_PATH_LITERAL("/file")), UploadNewFileOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, + &file_resource), + google_apis::ProgressCallback()); + EXPECT_EQ(google_apis::DRIVE_OTHER_ERROR, error); + } + { + google_apis::DriveApiErrorCode error = google_apis::HTTP_SUCCESS; + scoped_ptr<google_apis::FileResource> file_resource; + configurator.MultipartUploadExistingFile( + "text/plain", 10, "resource_id", + base::FilePath(FILE_PATH_LITERAL("/file")), UploadExistingFileOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, + &file_resource), + google_apis::ProgressCallback()); + EXPECT_EQ(google_apis::DRIVE_OTHER_ERROR, error); + } +} + +} // namespace drive diff --git a/components/drive/service/drive_service_interface.cc b/components/drive/service/drive_service_interface.cc new file mode 100644 index 0000000..76c1f20 --- /dev/null +++ b/components/drive/service/drive_service_interface.cc @@ -0,0 +1,27 @@ +// Copyright 2014 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/service/drive_service_interface.h" + +namespace drive { + +AddNewDirectoryOptions::AddNewDirectoryOptions() { +} + +AddNewDirectoryOptions::~AddNewDirectoryOptions() { +} + +UploadNewFileOptions::UploadNewFileOptions() { +} + +UploadNewFileOptions::~UploadNewFileOptions() { +} + +UploadExistingFileOptions::UploadExistingFileOptions() { +} + +UploadExistingFileOptions::~UploadExistingFileOptions() { +} + +} // namespace drive diff --git a/components/drive/service/drive_service_interface.h b/components/drive/service/drive_service_interface.h new file mode 100644 index 0000000..1c0dadd --- /dev/null +++ b/components/drive/service/drive_service_interface.h @@ -0,0 +1,467 @@ +// 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. + +#ifndef COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_ +#define COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_ + +#include <string> + +#include "base/time/time.h" +#include "google_apis/drive/auth_service_interface.h" +#include "google_apis/drive/base_requests.h" +#include "google_apis/drive/drive_api_requests.h" +#include "google_apis/drive/drive_common_callbacks.h" + +namespace base { +class Time; +} + +namespace drive { + +// Observer interface for DriveServiceInterface. +class DriveServiceObserver { + public: + // Triggered when the service gets ready to send requests. + virtual void OnReadyToSendRequests() {} + + // Called when the refresh token was found to be invalid. + virtual void OnRefreshTokenInvalid() {} + + protected: + virtual ~DriveServiceObserver() {} +}; + +// Optional parameters for AddNewDirectory(). +struct AddNewDirectoryOptions { + AddNewDirectoryOptions(); + ~AddNewDirectoryOptions(); + + // modified_date of the directory. + // Pass the null Time if you are not interested in setting this property. + base::Time modified_date; + + // last_viewed_by_me_date of the directory. + // Pass the null Time if you are not interested in setting this property. + base::Time last_viewed_by_me_date; + + // List of properties for a new directory. + google_apis::drive::Properties properties; +}; + +// Optional parameters for InitiateUploadNewFile() and +// MultipartUploadNewFile(). +struct UploadNewFileOptions { + UploadNewFileOptions(); + ~UploadNewFileOptions(); + + // modified_date of the file. + // Pass the null Time if you are not interested in setting this property. + base::Time modified_date; + + // last_viewed_by_me_date of the file. + // Pass the null Time if you are not interested in setting this property. + base::Time last_viewed_by_me_date; + + // List of properties for a new file. + google_apis::drive::Properties properties; +}; + +// Optional parameters for InitiateUploadExistingFile() and +// MultipartUploadExistingFile(). +struct UploadExistingFileOptions { + UploadExistingFileOptions(); + ~UploadExistingFileOptions(); + + // Expected ETag of the file. UPLOAD_ERROR_CONFLICT error is generated when + // matching fails. + // Pass the empty string to disable this behavior. + std::string etag; + + // New parent of the file. + // Pass the empty string to keep the property unchanged. + std::string parent_resource_id; + + // New title of the file. + // Pass the empty string to keep the property unchanged. + std::string title; + + // New modified_date of the file. + // Pass the null Time if you are not interested in setting this property. + base::Time modified_date; + + // New last_viewed_by_me_date of the file. + // Pass the null Time if you are not interested in setting this property. + base::Time last_viewed_by_me_date; + + // List of new properties for an existing file (it will be merged with + // existing properties). + google_apis::drive::Properties properties; +}; + +// Interface where we define operations that can be sent in batch requests. +class DriveServiceBatchOperationsInterface { + public: + virtual ~DriveServiceBatchOperationsInterface() {} + + // Uploads a file by a single request with multipart body. It's more efficient + // for small files than using |InitiateUploadNewFile| and |ResumeUpload|. + // |content_type| and |content_length| should be the ones of the file to be + // uploaded. |callback| must not be null. |progress_callback| may be null. + virtual google_apis::CancelCallback MultipartUploadNewFile( + const std::string& content_type, + int64 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) = 0; + + // Uploads a file by a single request with multipart body. It's more efficient + // for small files than using |InitiateUploadExistingFile| and |ResumeUpload|. + // |content_type| and |content_length| should be the ones of the file to be + // uploaded. |callback| must not be null. |progress_callback| may be null. + virtual google_apis::CancelCallback MultipartUploadExistingFile( + const std::string& content_type, + int64 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) = 0; +}; + +// Builder returned by DriveServiceInterface to build batch request. +class BatchRequestConfiguratorInterface + : public DriveServiceBatchOperationsInterface { + public: + ~BatchRequestConfiguratorInterface() override {} + + // Commits and sends the batch request. + virtual void Commit() = 0; +}; + +// This defines an interface for sharing by DriveService and MockDriveService +// so that we can do testing of clients of DriveService. +// +// All functions must be called on UI thread. DriveService is built on top of +// URLFetcher that runs on UI thread. +class DriveServiceInterface : public DriveServiceBatchOperationsInterface { + public: + ~DriveServiceInterface() override {} + + // Common service: + + // Initializes the documents service with |account_id|. + virtual void Initialize(const std::string& account_id) = 0; + + // Adds an observer. + virtual void AddObserver(DriveServiceObserver* observer) = 0; + + // Removes an observer. + virtual void RemoveObserver(DriveServiceObserver* observer) = 0; + + // True if ready to send requests. + virtual bool CanSendRequest() const = 0; + + // Authentication service: + + // True if OAuth2 access token is retrieved and believed to be fresh. + virtual bool HasAccessToken() const = 0; + + // Gets the cached OAuth2 access token or if empty, then fetches a new one. + virtual void RequestAccessToken( + const google_apis::AuthStatusCallback& callback) = 0; + + // True if OAuth2 refresh token is present. + virtual bool HasRefreshToken() const = 0; + + // Clears OAuth2 access token. + virtual void ClearAccessToken() = 0; + + // Clears OAuth2 refresh token. + virtual void ClearRefreshToken() = 0; + + // Document access: + + // Returns the resource id for the root directory. + virtual std::string GetRootResourceId() const = 0; + + // Fetches a file list of the account. |callback| will be called upon + // completion. + // If the list is too long, it may be paged. In such a case, a URL to fetch + // remaining results will be included in the returned result. See also + // GetRemainingFileList. + // + // |callback| must not be null. + virtual google_apis::CancelCallback GetAllFileList( + const google_apis::FileListCallback& callback) = 0; + + // Fetches a file list in the directory with |directory_resource_id|. + // |callback| will be called upon completion. + // If the list is too long, it may be paged. In such a case, a URL to fetch + // remaining results will be included in the returned result. See also + // GetRemainingFileList. + // + // |directory_resource_id| must not be empty. + // |callback| must not be null. + virtual google_apis::CancelCallback GetFileListInDirectory( + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) = 0; + + // Searches the resources for the |search_query| from all the user's + // resources. |callback| will be called upon completion. + // If the list is too long, it may be paged. In such a case, a URL to fetch + // remaining results will be included in the returned result. See also + // GetRemainingFileList. + // + // |search_query| must not be empty. + // |callback| must not be null. + virtual google_apis::CancelCallback Search( + const std::string& search_query, + const google_apis::FileListCallback& callback) = 0; + + // Searches the resources with the |title|. + // |directory_resource_id| is an optional parameter. If it is empty, + // the search target is all the existing resources. Otherwise, it is + // the resources directly under the directory with |directory_resource_id|. + // If the list is too long, it may be paged. In such a case, a URL to fetch + // remaining results will be included in the returned result. See also + // GetRemainingFileList. + // + // |title| must not be empty, and |callback| must not be null. + virtual google_apis::CancelCallback SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) = 0; + + // Fetches change list since |start_changestamp|. |callback| will be + // called upon completion. + // If the list is too long, it may be paged. In such a case, a URL to fetch + // remaining results will be included in the returned result. See also + // GetRemainingChangeList. + // + // |callback| must not be null. + virtual google_apis::CancelCallback GetChangeList( + int64 start_changestamp, + const google_apis::ChangeListCallback& callback) = 0; + + // The result of GetChangeList() may be paged. + // In such a case, a next link to fetch remaining result is returned. + // The page token can be used for this method. |callback| will be called upon + // completion. + // + // |next_link| must not be empty. |callback| must not be null. + virtual google_apis::CancelCallback GetRemainingChangeList( + const GURL& next_link, + const google_apis::ChangeListCallback& callback) = 0; + + // The result of GetAllFileList(), GetFileListInDirectory(), Search() + // and SearchByTitle() may be paged. In such a case, a next link to fetch + // remaining result is returned. The page token can be used for this method. + // |callback| will be called upon completion. + // + // |next_link| must not be empty. |callback| must not be null. + virtual google_apis::CancelCallback GetRemainingFileList( + const GURL& next_link, + const google_apis::FileListCallback& callback) = 0; + + // Fetches single entry metadata from server. The entry's file id equals + // |resource_id|. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback GetFileResource( + const std::string& resource_id, + const google_apis::FileResourceCallback& callback) = 0; + + // Fetches an url for the sharing dialog for a single entry with id + // |resource_id|, to be embedded in a webview or an iframe with origin + // |embed_origin|. The url is returned via |callback| with results on the + // calling thread. |callback| must not be null. + virtual google_apis::CancelCallback GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const google_apis::GetShareUrlCallback& callback) = 0; + + // Gets the about resource information from the server. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback GetAboutResource( + const google_apis::AboutResourceCallback& callback) = 0; + + // Gets the application information from the server. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback GetAppList( + const google_apis::AppListCallback& callback) = 0; + + // Permanently deletes a resource identified by its |resource_id|. + // If |etag| is not empty and did not match, the deletion fails with + // HTTP_PRECONDITION error. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback DeleteResource( + const std::string& resource_id, + const std::string& etag, + const google_apis::EntryActionCallback& callback) = 0; + + // Trashes a resource identified by its |resource_id|. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback TrashResource( + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) = 0; + + // Makes a copy of a resource with |resource_id|. + // The new resource will be put under a directory with |parent_resource_id|, + // and it'll be named |new_title|. + // If |last_modified| is not null, the modified date of the resource on the + // server will be set to the date. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const google_apis::FileResourceCallback& callback) = 0; + + // Updates a resource with |resource_id| to the directory of + // |parent_resource_id| with renaming to |new_title|. + // If |last_modified| or |last_accessed| is not null, the modified/accessed + // date of the resource on the server will be set to the date. + // If |properties| are specified, then they will be set on |resource_id|. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) = 0; + + // Adds a resource (document, file, or collection) identified by its + // |resource_id| to a collection represented by the |parent_resource_id|. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) = 0; + + // Removes a resource (document, file, collection) identified by its + // |resource_id| from a collection represented by the |parent_resource_id|. + // Upon completion, invokes |callback| with results on the calling thread. + // |callback| must not be null. + virtual google_apis::CancelCallback RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) = 0; + + // Adds new collection with |directory_title| under parent directory + // identified with |parent_resource_id|. |parent_resource_id| can be the + // value returned by GetRootResourceId to represent the root directory. + // Upon completion, invokes |callback| and passes newly created entry on + // the calling thread. + // This function cannot be named as "CreateDirectory" as it conflicts with + // a macro on Windows. + // |callback| must not be null. + virtual google_apis::CancelCallback AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const google_apis::FileResourceCallback& callback) = 0; + + // Downloads a file with |resourced_id|. The downloaded file will + // be stored at |local_cache_path| location. Upon completion, invokes + // |download_action_callback| with results on the calling thread. + // If |get_content_callback| is not empty, + // URLFetcherDelegate::OnURLFetchDownloadData will be called, which will in + // turn invoke |get_content_callback| on the calling thread. + // If |progress_callback| is not empty, it is invoked periodically when + // the download made some progress. + // + // |download_action_callback| must not be null. + // |get_content_callback| and |progress_callback| may be null. + virtual google_apis::CancelCallback DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const google_apis::DownloadActionCallback& download_action_callback, + const google_apis::GetContentCallback& get_content_callback, + const google_apis::ProgressCallback& progress_callback) = 0; + + // Initiates uploading of a new document/file. + // |content_type| and |content_length| should be the ones of the file to be + // uploaded. + // |callback| must not be null. + virtual google_apis::CancelCallback InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const google_apis::InitiateUploadCallback& callback) = 0; + + // Initiates uploading of an existing document/file. + // |content_type| and |content_length| should be the ones of the file to be + // uploaded. + // |callback| must not be null. + virtual google_apis::CancelCallback InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const google_apis::InitiateUploadCallback& callback) = 0; + + // Resumes uploading of a document/file on the calling thread. + // |callback| must not be null. |progress_callback| may be null. + virtual google_apis::CancelCallback ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const google_apis::drive::UploadRangeCallback& callback, + const google_apis::ProgressCallback& progress_callback) = 0; + + // Gets the current status of the uploading to |upload_url| from the server. + // |drive_file_path| and |content_length| should be set to the same value + // which is used for ResumeUpload. + // |callback| must not be null. + virtual google_apis::CancelCallback GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const google_apis::drive::UploadRangeCallback& callback) = 0; + + // Authorizes a Drive app with the id |app_id| to open the given file. + // Upon completion, invokes |callback| with the link to open the file with + // the provided app. |callback| must not be null. + virtual google_apis::CancelCallback AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const google_apis::AuthorizeAppCallback& callback) = 0; + + // Uninstalls a Drive app with the id |app_id|. |callback| must not be null. + virtual google_apis::CancelCallback UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) = 0; + + // Authorizes the account |email| to access |resource_id| as a |role|. + // |callback| must not be null. + virtual google_apis::CancelCallback AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) = 0; + + // Starts batch request and returns |BatchRequestConfigurator|. + virtual scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() = 0; +}; + +} // namespace drive + +#endif // COMPONENTS_DRIVE_SERVICE_DRIVE_SERVICE_INTERFACE_H_ diff --git a/components/drive/service/dummy_drive_service.cc b/components/drive/service/dummy_drive_service.cc new file mode 100644 index 0000000..8700fce --- /dev/null +++ b/components/drive/service/dummy_drive_service.cc @@ -0,0 +1,224 @@ +// 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/service/dummy_drive_service.h" + +#include "base/bind.h" + +using google_apis::AboutResourceCallback; +using google_apis::AppListCallback; +using google_apis::AuthStatusCallback; +using google_apis::AuthorizeAppCallback; +using google_apis::CancelCallback; +using google_apis::ChangeListCallback; +using google_apis::DownloadActionCallback; +using google_apis::EntryActionCallback; +using google_apis::FileListCallback; +using google_apis::FileResourceCallback; +using google_apis::GetContentCallback; +using google_apis::GetShareUrlCallback; +using google_apis::InitiateUploadCallback; +using google_apis::ProgressCallback; +using google_apis::drive::UploadRangeCallback; + +namespace drive { + +DummyDriveService::DummyDriveService() {} + +DummyDriveService::~DummyDriveService() {} + +void DummyDriveService::Initialize(const std::string& account_id) {} + +void DummyDriveService::AddObserver(DriveServiceObserver* observer) {} + +void DummyDriveService::RemoveObserver(DriveServiceObserver* observer) {} + +bool DummyDriveService::CanSendRequest() const { return true; } + +bool DummyDriveService::HasAccessToken() const { return true; } + +void DummyDriveService::RequestAccessToken(const AuthStatusCallback& callback) { + callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token"); +} + +bool DummyDriveService::HasRefreshToken() const { return true; } + +void DummyDriveService::ClearAccessToken() { } + +void DummyDriveService::ClearRefreshToken() { } + +std::string DummyDriveService::GetRootResourceId() const { + return "dummy_root"; +} + +CancelCallback DummyDriveService::GetAllFileList( + const FileListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetFileListInDirectory( + const std::string& directory_resource_id, + const FileListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::Search( + const std::string& search_query, + const FileListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const FileListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetChangeList( + int64 start_changestamp, + const ChangeListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetRemainingChangeList( + const GURL& next_link, + const ChangeListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetRemainingFileList( + const GURL& next_link, + const FileListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetFileResource( + const std::string& resource_id, + const FileResourceCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const GetShareUrlCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetAboutResource( + const AboutResourceCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetAppList( + const AppListCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::DeleteResource( + const std::string& resource_id, + const std::string& etag, + const EntryActionCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::TrashResource( + const std::string& resource_id, + const EntryActionCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const DownloadActionCallback& download_action_callback, + const GetContentCallback& get_content_callback, + const ProgressCallback& progress_callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const FileResourceCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) { + return CancelCallback(); +} + +CancelCallback DummyDriveService::AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const FileResourceCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const InitiateUploadCallback& callback) { + return CancelCallback(); +} + +CancelCallback DummyDriveService::InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const InitiateUploadCallback& callback) { + return CancelCallback(); +} + +CancelCallback DummyDriveService::ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const UploadRangeCallback& callback, + const ProgressCallback& progress_callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const UploadRangeCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::MultipartUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const base::FilePath& local_file_path, + const UploadNewFileOptions& options, + const FileResourceCallback& callback, + const ProgressCallback& progress_callback) { + return CancelCallback(); +} + +CancelCallback DummyDriveService::MultipartUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const base::FilePath& local_file_path, + const UploadExistingFileOptions& options, + const FileResourceCallback& callback, + const ProgressCallback& progress_callback) { + return CancelCallback(); +} + +CancelCallback DummyDriveService::AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const AuthorizeAppCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::UninstallApp( + const std::string& app_id, + const EntryActionCallback& callback) { return CancelCallback(); } + +CancelCallback DummyDriveService::AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const EntryActionCallback& callback) { return CancelCallback(); } +scoped_ptr<BatchRequestConfiguratorInterface> +DummyDriveService::StartBatchRequest() { + return scoped_ptr<BatchRequestConfiguratorInterface>(); +} + +} // namespace drive diff --git a/components/drive/service/dummy_drive_service.h b/components/drive/service/dummy_drive_service.h new file mode 100644 index 0000000..3317c45 --- /dev/null +++ b/components/drive/service/dummy_drive_service.h @@ -0,0 +1,166 @@ +// 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. + +#ifndef COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_ +#define COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_ + +#include <string> + +#include "components/drive/service/drive_service_interface.h" +#include "google_apis/drive/auth_service_interface.h" + +namespace drive { + +// Dummy implementation of DriveServiceInterface. +// All functions do nothing, or return place holder values like 'true'. +class DummyDriveService : public DriveServiceInterface { + public: + DummyDriveService(); + ~DummyDriveService() override; + + // DriveServiceInterface Overrides + void Initialize(const std::string& account_id) override; + void AddObserver(DriveServiceObserver* observer) override; + void RemoveObserver(DriveServiceObserver* observer) override; + bool CanSendRequest() const override; + bool HasAccessToken() const override; + void RequestAccessToken( + const google_apis::AuthStatusCallback& callback) override; + bool HasRefreshToken() const override; + void ClearAccessToken() override; + void ClearRefreshToken() override; + std::string GetRootResourceId() const override; + google_apis::CancelCallback GetAllFileList( + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileListInDirectory( + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback Search( + const std::string& search_query, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetChangeList( + int64 start_changestamp, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingChangeList( + const GURL& next_link, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingFileList( + const GURL& next_link, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileResource( + const std::string& resource_id, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const google_apis::GetShareUrlCallback& callback) override; + google_apis::CancelCallback GetAboutResource( + const google_apis::AboutResourceCallback& callback) override; + google_apis::CancelCallback GetAppList( + const google_apis::AppListCallback& callback) override; + google_apis::CancelCallback DeleteResource( + const std::string& resource_id, + const std::string& etag, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback TrashResource( + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const google_apis::DownloadActionCallback& download_action_callback, + const google_apis::GetContentCallback& get_content_callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const google_apis::drive::UploadRangeCallback& callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const google_apis::drive::UploadRangeCallback& callback) override; + google_apis::CancelCallback MultipartUploadNewFile( + const std::string& content_type, + int64 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; + google_apis::CancelCallback MultipartUploadExistingFile( + const std::string& content_type, + int64 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; + google_apis::CancelCallback AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const google_apis::AuthorizeAppCallback& callback) override; + google_apis::CancelCallback UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) override; + scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override; +}; + +} // namespace drive + +#endif // COMPONENTS_DRIVE_SERVICE_DUMMY_DRIVE_SERVICE_H_ diff --git a/components/drive/service/fake_drive_service.cc b/components/drive/service/fake_drive_service.cc new file mode 100644 index 0000000..81b55d1 --- /dev/null +++ b/components/drive/service/fake_drive_service.cc @@ -0,0 +1,1787 @@ +// 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/service/fake_drive_service.h" + +#include <string> + +#include "base/files/file_util.h" +#include "base/json/json_string_value_serializer.h" +#include "base/logging.h" +#include "base/md5.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_tokenizer.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "base/values.h" +#include "components/drive/drive_api_util.h" +#include "google_apis/drive/drive_api_parser.h" +#include "google_apis/drive/test_util.h" +#include "net/base/escape.h" +#include "net/base/url_util.h" + +using google_apis::AboutResource; +using google_apis::AboutResourceCallback; +using google_apis::AppList; +using google_apis::AppListCallback; +using google_apis::AuthStatusCallback; +using google_apis::AuthorizeAppCallback; +using google_apis::CancelCallback; +using google_apis::ChangeList; +using google_apis::ChangeListCallback; +using google_apis::ChangeResource; +using google_apis::DownloadActionCallback; +using google_apis::EntryActionCallback; +using google_apis::FileList; +using google_apis::FileListCallback; +using google_apis::FileResource; +using google_apis::FileResourceCallback; +using google_apis::DRIVE_FILE_ERROR; +using google_apis::DRIVE_NO_CONNECTION; +using google_apis::DRIVE_OTHER_ERROR; +using google_apis::DriveApiErrorCode; +using google_apis::GetContentCallback; +using google_apis::GetShareUrlCallback; +using google_apis::HTTP_BAD_REQUEST; +using google_apis::HTTP_CREATED; +using google_apis::HTTP_FORBIDDEN; +using google_apis::HTTP_NOT_FOUND; +using google_apis::HTTP_NO_CONTENT; +using google_apis::HTTP_PRECONDITION; +using google_apis::HTTP_RESUME_INCOMPLETE; +using google_apis::HTTP_SUCCESS; +using google_apis::InitiateUploadCallback; +using google_apis::ParentReference; +using google_apis::ProgressCallback; +using google_apis::UploadRangeResponse; +using google_apis::drive::UploadRangeCallback; +namespace test_util = google_apis::test_util; + +namespace drive { +namespace { + +// Returns true if the entry matches with the search query. +// Supports queries consist of following format. +// - Phrases quoted by double/single quotes +// - AND search for multiple words/phrases segmented by space +// - Limited attribute search. Only "title:" is supported. +bool EntryMatchWithQuery(const ChangeResource& entry, + const std::string& query) { + base::StringTokenizer tokenizer(query, " "); + tokenizer.set_quote_chars("\"'"); + while (tokenizer.GetNext()) { + std::string key, value; + const std::string& token = tokenizer.token(); + if (token.find(':') == std::string::npos) { + base::TrimString(token, "\"'", &value); + } else { + base::StringTokenizer key_value(token, ":"); + key_value.set_quote_chars("\"'"); + if (!key_value.GetNext()) + return false; + key = key_value.token(); + if (!key_value.GetNext()) + return false; + base::TrimString(key_value.token(), "\"'", &value); + } + + // TODO(peria): Deal with other attributes than title. + if (!key.empty() && key != "title") + return false; + // Search query in the title. + if (!entry.file() || + entry.file()->title().find(value) == std::string::npos) + return false; + } + return true; +} + +void ScheduleUploadRangeCallback(const UploadRangeCallback& callback, + int64 start_position, + int64 end_position, + DriveApiErrorCode error, + scoped_ptr<FileResource> entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + UploadRangeResponse(error, + start_position, + end_position), + base::Passed(&entry))); +} + +void FileListCallbackAdapter(const FileListCallback& callback, + DriveApiErrorCode error, + scoped_ptr<ChangeList> change_list) { + scoped_ptr<FileList> file_list; + if (!change_list) { + callback.Run(error, file_list.Pass()); + return; + } + + file_list.reset(new FileList); + file_list->set_next_link(change_list->next_link()); + for (size_t i = 0; i < change_list->items().size(); ++i) { + const ChangeResource& entry = *change_list->items()[i]; + if (entry.file()) + file_list->mutable_items()->push_back(new FileResource(*entry.file())); + } + callback.Run(error, file_list.Pass()); +} + +bool UserHasWriteAccess(google_apis::drive::PermissionRole user_permission) { + switch (user_permission) { + case google_apis::drive::PERMISSION_ROLE_OWNER: + case google_apis::drive::PERMISSION_ROLE_WRITER: + return true; + case google_apis::drive::PERMISSION_ROLE_READER: + case google_apis::drive::PERMISSION_ROLE_COMMENTER: + break; + } + return false; +} + +void CallFileResouceCallback(const FileResourceCallback& callback, + const UploadRangeResponse& response, + scoped_ptr<FileResource> entry) { + callback.Run(response.code, entry.Pass()); +} + +struct CallResumeUpload { + CallResumeUpload() {} + ~CallResumeUpload() {} + + void Run(DriveApiErrorCode code, const GURL& upload_url) { + if (service) { + service->ResumeUpload( + upload_url, + /* start position */ 0, + /* end position */ content_length, + content_length, + content_type, + local_file_path, + base::Bind(&CallFileResouceCallback, callback), + progress_callback); + } + } + + base::WeakPtr<FakeDriveService> service; + int64 content_length; + std::string content_type; + base::FilePath local_file_path; + FileResourceCallback callback; + ProgressCallback progress_callback; +}; + +} // namespace + +struct FakeDriveService::EntryInfo { + EntryInfo() : user_permission(google_apis::drive::PERMISSION_ROLE_OWNER) {} + + google_apis::ChangeResource change_resource; + GURL share_url; + std::string content_data; + + // Behaves in the same way as "userPermission" described in + // https://developers.google.com/drive/v2/reference/files + google_apis::drive::PermissionRole user_permission; +}; + +struct FakeDriveService::UploadSession { + std::string content_type; + int64 content_length; + std::string parent_resource_id; + std::string resource_id; + std::string etag; + std::string title; + + int64 uploaded_size; + + UploadSession() + : content_length(0), + uploaded_size(0) {} + + UploadSession( + std::string content_type, + int64 content_length, + std::string parent_resource_id, + std::string resource_id, + std::string etag, + std::string title) + : content_type(content_type), + content_length(content_length), + parent_resource_id(parent_resource_id), + resource_id(resource_id), + etag(etag), + title(title), + uploaded_size(0) { + } +}; + +FakeDriveService::FakeDriveService() + : about_resource_(new AboutResource), + published_date_seq_(0), + next_upload_sequence_number_(0), + default_max_results_(0), + resource_id_count_(0), + file_list_load_count_(0), + change_list_load_count_(0), + directory_load_count_(0), + about_resource_load_count_(0), + app_list_load_count_(0), + blocked_file_list_load_count_(0), + offline_(false), + never_return_all_file_list_(false), + share_url_base_("https://share_url/"), + weak_ptr_factory_(this) { + about_resource_->set_largest_change_id(654321); + about_resource_->set_quota_bytes_total(9876543210); + about_resource_->set_quota_bytes_used_aggregate(6789012345); + about_resource_->set_root_folder_id(GetRootResourceId()); +} + +FakeDriveService::~FakeDriveService() { + DCHECK(thread_checker_.CalledOnValidThread()); + STLDeleteValues(&entries_); +} + +bool FakeDriveService::LoadAppListForDriveApi( + const std::string& relative_path) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Load JSON data, which must be a dictionary. + scoped_ptr<base::Value> value = test_util::LoadJSONFile(relative_path); + CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType()); + app_info_value_.reset( + static_cast<base::DictionaryValue*>(value.release())); + return app_info_value_; +} + +void FakeDriveService::AddApp(const std::string& app_id, + const std::string& app_name, + const std::string& product_id, + const std::string& create_url, + bool is_removable) { + if (app_json_template_.empty()) { + base::FilePath path = + test_util::GetTestFilePath("drive/applist_app_template.json"); + CHECK(base::ReadFileToString(path, &app_json_template_)); + } + + std::string app_json = app_json_template_; + base::ReplaceSubstringsAfterOffset(&app_json, 0, "$AppId", app_id); + base::ReplaceSubstringsAfterOffset(&app_json, 0, "$AppName", app_name); + base::ReplaceSubstringsAfterOffset(&app_json, 0, "$ProductId", product_id); + base::ReplaceSubstringsAfterOffset(&app_json, 0, "$CreateUrl", create_url); + base::ReplaceSubstringsAfterOffset( + &app_json, 0, "$Removable", is_removable ? "true" : "false"); + + JSONStringValueDeserializer json(app_json); + std::string error_message; + scoped_ptr<base::Value> value(json.Deserialize(NULL, &error_message)); + CHECK_EQ(base::Value::TYPE_DICTIONARY, value->GetType()); + + base::ListValue* item_list; + CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list)); + item_list->Append(value.release()); +} + +void FakeDriveService::RemoveAppByProductId(const std::string& product_id) { + base::ListValue* item_list; + CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list)); + for (size_t i = 0; i < item_list->GetSize(); ++i) { + base::DictionaryValue* item; + CHECK(item_list->GetDictionary(i, &item)); + const char kKeyProductId[] = "productId"; + std::string item_product_id; + if (item->GetStringWithoutPathExpansion(kKeyProductId, &item_product_id) && + product_id == item_product_id) { + item_list->Remove(i, NULL); + return; + } + } +} + +bool FakeDriveService::HasApp(const std::string& app_id) const { + base::ListValue* item_list; + CHECK(app_info_value_->GetListWithoutPathExpansion("items", &item_list)); + for (size_t i = 0; i < item_list->GetSize(); ++i) { + base::DictionaryValue* item; + CHECK(item_list->GetDictionary(i, &item)); + const char kKeyId[] = "id"; + std::string item_id; + if (item->GetStringWithoutPathExpansion(kKeyId, &item_id) && + item_id == app_id) { + return true; + } + } + + return false; +} + +void FakeDriveService::SetQuotaValue(int64 used, int64 total) { + DCHECK(thread_checker_.CalledOnValidThread()); + + about_resource_->set_quota_bytes_used_aggregate(used); + about_resource_->set_quota_bytes_total(total); +} + +GURL FakeDriveService::GetFakeLinkUrl(const std::string& resource_id) { + return GURL("https://fake_server/" + net::EscapePath(resource_id)); +} + +void FakeDriveService::Initialize(const std::string& account_id) { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void FakeDriveService::AddObserver(DriveServiceObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void FakeDriveService::RemoveObserver(DriveServiceObserver* observer) { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +bool FakeDriveService::CanSendRequest() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return true; +} + +bool FakeDriveService::HasAccessToken() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return true; +} + +void FakeDriveService::RequestAccessToken(const AuthStatusCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + callback.Run(google_apis::HTTP_NOT_MODIFIED, "fake_access_token"); +} + +bool FakeDriveService::HasRefreshToken() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return true; +} + +void FakeDriveService::ClearAccessToken() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void FakeDriveService::ClearRefreshToken() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +std::string FakeDriveService::GetRootResourceId() const { + return "fake_root"; +} + +CancelCallback FakeDriveService::GetAllFileList( + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (never_return_all_file_list_) { + ++blocked_file_list_load_count_; + return CancelCallback(); + } + + GetChangeListInternal(0, // start changestamp + std::string(), // empty search query + std::string(), // no directory resource id, + 0, // start offset + default_max_results_, + &file_list_load_count_, + base::Bind(&FileListCallbackAdapter, callback)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetFileListInDirectory( + const std::string& directory_resource_id, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!directory_resource_id.empty()); + DCHECK(!callback.is_null()); + + GetChangeListInternal(0, // start changestamp + std::string(), // empty search query + directory_resource_id, + 0, // start offset + default_max_results_, + &directory_load_count_, + base::Bind(&FileListCallbackAdapter, callback)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::Search( + const std::string& search_query, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!search_query.empty()); + DCHECK(!callback.is_null()); + + GetChangeListInternal(0, // start changestamp + search_query, + std::string(), // no directory resource id, + 0, // start offset + default_max_results_, + NULL, + base::Bind(&FileListCallbackAdapter, callback)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!title.empty()); + DCHECK(!callback.is_null()); + + // Note: the search implementation here doesn't support quotation unescape, + // so don't escape here. + GetChangeListInternal(0, // start changestamp + base::StringPrintf("title:'%s'", title.c_str()), + directory_resource_id, + 0, // start offset + default_max_results_, + NULL, + base::Bind(&FileListCallbackAdapter, callback)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetChangeList( + int64 start_changestamp, + const ChangeListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + GetChangeListInternal(start_changestamp, + std::string(), // empty search query + std::string(), // no directory resource id, + 0, // start offset + default_max_results_, + &change_list_load_count_, + callback); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetRemainingChangeList( + const GURL& next_link, + const ChangeListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!next_link.is_empty()); + DCHECK(!callback.is_null()); + + // "changestamp", "q", "parent" and "start-offset" are parameters to + // implement "paging" of the result on FakeDriveService. + // The URL should be the one filled in GetChangeListInternal of the + // previous method invocation, so it should start with "http://localhost/?". + // See also GetChangeListInternal. + DCHECK_EQ(next_link.host(), "localhost"); + DCHECK_EQ(next_link.path(), "/"); + + int64 start_changestamp = 0; + std::string search_query; + std::string directory_resource_id; + int start_offset = 0; + int max_results = default_max_results_; + base::StringPairs parameters; + if (base::SplitStringIntoKeyValuePairs( + next_link.query(), '=', '&', ¶meters)) { + for (size_t i = 0; i < parameters.size(); ++i) { + if (parameters[i].first == "changestamp") { + base::StringToInt64(parameters[i].second, &start_changestamp); + } else if (parameters[i].first == "q") { + search_query = + net::UnescapeURLComponent(parameters[i].second, + net::UnescapeRule::URL_SPECIAL_CHARS); + } else if (parameters[i].first == "parent") { + directory_resource_id = + net::UnescapeURLComponent(parameters[i].second, + net::UnescapeRule::URL_SPECIAL_CHARS); + } else if (parameters[i].first == "start-offset") { + base::StringToInt(parameters[i].second, &start_offset); + } else if (parameters[i].first == "max-results") { + base::StringToInt(parameters[i].second, &max_results); + } + } + } + + GetChangeListInternal(start_changestamp, search_query, directory_resource_id, + start_offset, max_results, NULL, callback); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetRemainingFileList( + const GURL& next_link, + const FileListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!next_link.is_empty()); + DCHECK(!callback.is_null()); + + return GetRemainingChangeList( + next_link, base::Bind(&FileListCallbackAdapter, callback)); +} + +CancelCallback FakeDriveService::GetFileResource( + const std::string& resource_id, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (entry && entry->change_resource.file()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, base::Passed(make_scoped_ptr( + new FileResource(*entry->change_resource.file()))))); + return CancelCallback(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetShareUrl( + const std::string& resource_id, + const GURL& /* embed_origin */, + const GetShareUrlCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + GURL())); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, entry->share_url)); + return CancelCallback(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, GURL())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetAboutResource( + const AboutResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + scoped_ptr<AboutResource> null; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, base::Passed(&null))); + return CancelCallback(); + } + + ++about_resource_load_count_; + scoped_ptr<AboutResource> about_resource(new AboutResource(*about_resource_)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + HTTP_SUCCESS, base::Passed(&about_resource))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetAppList(const AppListCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + DCHECK(app_info_value_); + + if (offline_) { + scoped_ptr<AppList> null; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(&null))); + return CancelCallback(); + } + + ++app_list_load_count_; + scoped_ptr<AppList> app_list(AppList::CreateFrom(*app_info_value_)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, base::Passed(&app_list))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::DeleteResource( + const std::string& resource_id, + const std::string& etag, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION)); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + const FileResource* file = change->file(); + if (change->is_deleted()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + if (!etag.empty() && etag != file->etag()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_PRECONDITION)); + return CancelCallback(); + } + + if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN)); + return CancelCallback(); + } + + change->set_deleted(true); + AddNewChangestamp(change); + change->set_file(scoped_ptr<FileResource>()); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::TrashResource( + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION)); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + FileResource* file = change->mutable_file(); + if (change->is_deleted() || file->labels().is_trashed()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + if (entry->user_permission != google_apis::drive::PERMISSION_ROLE_OWNER) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_FORBIDDEN)); + return CancelCallback(); + } + + file->mutable_labels()->set_trashed(true); + AddNewChangestamp(change); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const DownloadActionCallback& download_action_callback, + const GetContentCallback& get_content_callback, + const ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!download_action_callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(download_action_callback, + DRIVE_NO_CONNECTION, + base::FilePath())); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry || entry->change_resource.file()->IsHostedDocument()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(download_action_callback, HTTP_NOT_FOUND, base::FilePath())); + return CancelCallback(); + } + + const FileResource* file = entry->change_resource.file(); + const std::string& content_data = entry->content_data; + int64 file_size = file->file_size(); + DCHECK_EQ(static_cast<size_t>(file_size), content_data.size()); + + if (!get_content_callback.is_null()) { + const int64 kBlockSize = 5; + for (int64 i = 0; i < file_size; i += kBlockSize) { + const int64 size = std::min(kBlockSize, file_size - i); + scoped_ptr<std::string> content_for_callback( + new std::string(content_data.substr(i, size))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(get_content_callback, HTTP_SUCCESS, + base::Passed(&content_for_callback))); + } + } + + if (!test_util::WriteStringToFile(local_cache_path, content_data)) { + // Failed to write the content. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(download_action_callback, + DRIVE_FILE_ERROR, base::FilePath())); + return CancelCallback(); + } + + if (!progress_callback.is_null()) { + // See also the comment in ResumeUpload(). For testing that clients + // can handle the case progress_callback is called multiple times, + // here we invoke the callback twice. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(progress_callback, file_size / 2, file_size)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(progress_callback, file_size, file_size)); + } + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(download_action_callback, + HTTP_SUCCESS, + local_cache_path)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::CopyResource( + const std::string& resource_id, + const std::string& in_parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + const std::string& parent_resource_id = in_parent_resource_id.empty() ? + GetRootResourceId() : in_parent_resource_id; + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + // Make a copy and set the new resource ID and the new title. + scoped_ptr<EntryInfo> copied_entry(new EntryInfo); + copied_entry->content_data = entry->content_data; + copied_entry->share_url = entry->share_url; + copied_entry->change_resource.set_file( + make_scoped_ptr(new FileResource(*entry->change_resource.file()))); + + ChangeResource* new_change = &copied_entry->change_resource; + FileResource* new_file = new_change->mutable_file(); + const std::string new_resource_id = GetNewResourceId(); + new_change->set_file_id(new_resource_id); + new_file->set_file_id(new_resource_id); + new_file->set_title(new_title); + + ParentReference parent; + parent.set_file_id(parent_resource_id); + parent.set_parent_link(GetFakeLinkUrl(parent_resource_id)); + std::vector<ParentReference> parents; + parents.push_back(parent); + *new_file->mutable_parents() = parents; + + if (!last_modified.is_null()) + new_file->set_modified_date(last_modified); + + AddNewChangestamp(new_change); + UpdateETag(new_file); + + // Add the new entry to the map. + entries_[new_resource_id] = copied_entry.release(); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + HTTP_SUCCESS, + base::Passed(make_scoped_ptr(new FileResource(*new_file))))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + if (!UserHasWriteAccess(entry->user_permission)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_FORBIDDEN, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + FileResource* file = change->mutable_file(); + + if (!new_title.empty()) + file->set_title(new_title); + + // Set parent if necessary. + if (!parent_resource_id.empty()) { + ParentReference parent; + parent.set_file_id(parent_resource_id); + parent.set_parent_link(GetFakeLinkUrl(parent_resource_id)); + + std::vector<ParentReference> parents; + parents.push_back(parent); + *file->mutable_parents() = parents; + } + + if (!last_modified.is_null()) + file->set_modified_date(last_modified); + + if (!last_viewed_by_me.is_null()) + file->set_last_viewed_by_me_date(last_viewed_by_me); + + AddNewChangestamp(change); + UpdateETag(file); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, + base::Passed(make_scoped_ptr(new FileResource(*file))))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION)); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + // On the real Drive server, resources do not necessary shape a tree + // structure. That is, each resource can have multiple parent. + // We mimic the behavior here; AddResourceToDirectoy just adds + // one more parent, not overwriting old ones. + ParentReference parent; + parent.set_file_id(parent_resource_id); + parent.set_parent_link(GetFakeLinkUrl(parent_resource_id)); + change->mutable_file()->mutable_parents()->push_back(parent); + + AddNewChangestamp(change); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_SUCCESS)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, DRIVE_NO_CONNECTION)); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + FileResource* file = change->mutable_file(); + std::vector<ParentReference>* parents = file->mutable_parents(); + for (size_t i = 0; i < parents->size(); ++i) { + if ((*parents)[i].file_id() == parent_resource_id) { + parents->erase(parents->begin() + i); + AddNewChangestamp(change); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NO_CONTENT)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); + } + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(callback, HTTP_NOT_FOUND)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const FileResourceCallback& callback) { + return AddNewDirectoryWithResourceId( + "", + parent_resource_id.empty() ? GetRootResourceId() : parent_resource_id, + directory_title, + options, + callback); +} + +CancelCallback FakeDriveService::InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const InitiateUploadCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, DRIVE_NO_CONNECTION, GURL())); + return CancelCallback(); + } + + if (parent_resource_id != GetRootResourceId() && + !entries_.count(parent_resource_id)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, GURL())); + return CancelCallback(); + } + + GURL session_url = GetNewUploadSessionUrl(); + upload_sessions_[session_url] = + UploadSession(content_type, content_length, + parent_resource_id, + "", // resource_id + "", // etag + title); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, session_url)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const InitiateUploadCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, DRIVE_NO_CONNECTION, GURL())); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, GURL())); + return CancelCallback(); + } + + if (!UserHasWriteAccess(entry->user_permission)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_FORBIDDEN, GURL())); + return CancelCallback(); + } + + FileResource* file = entry->change_resource.mutable_file(); + if (!options.etag.empty() && options.etag != file->etag()) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_PRECONDITION, GURL())); + return CancelCallback(); + } + // TODO(hashimoto): Update |file|'s metadata with |options|. + + GURL session_url = GetNewUploadSessionUrl(); + upload_sessions_[session_url] = + UploadSession(content_type, content_length, + "", // parent_resource_id + resource_id, + file->etag(), + "" /* title */); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, session_url)); + return CancelCallback(); +} + +CancelCallback FakeDriveService::GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const UploadRangeCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + return CancelCallback(); +} + +CancelCallback FakeDriveService::ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const UploadRangeCallback& callback, + const ProgressCallback& progress_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + FileResourceCallback completion_callback + = base::Bind(&ScheduleUploadRangeCallback, + callback, start_position, end_position); + + if (offline_) { + completion_callback.Run(DRIVE_NO_CONNECTION, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + if (!upload_sessions_.count(upload_url)) { + completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + UploadSession* session = &upload_sessions_[upload_url]; + + // Chunks are required to be sent in such a ways that they fill from the start + // of the not-yet-uploaded part with no gaps nor overlaps. + if (session->uploaded_size != start_position) { + completion_callback.Run(HTTP_BAD_REQUEST, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + if (!progress_callback.is_null()) { + // In the real GDataWapi/Drive DriveService, progress is reported in + // nondeterministic timing. In this fake implementation, we choose to call + // it twice per one ResumeUpload. This is for making sure that client code + // works fine even if the callback is invoked more than once; it is the + // crucial difference of the progress callback from others. + // Note that progress is notified in the relative offset in each chunk. + const int64 chunk_size = end_position - start_position; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(progress_callback, chunk_size / 2, chunk_size)); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(progress_callback, chunk_size, chunk_size)); + } + + if (content_length != end_position) { + session->uploaded_size = end_position; + completion_callback.Run(HTTP_RESUME_INCOMPLETE, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + std::string content_data; + if (!base::ReadFileToString(local_file_path, &content_data)) { + session->uploaded_size = end_position; + completion_callback.Run(DRIVE_FILE_ERROR, scoped_ptr<FileResource>()); + return CancelCallback(); + } + session->uploaded_size = end_position; + + // |resource_id| is empty if the upload is for new file. + if (session->resource_id.empty()) { + DCHECK(!session->parent_resource_id.empty()); + DCHECK(!session->title.empty()); + const EntryInfo* new_entry = AddNewEntry( + "", // auto generate resource id. + session->content_type, + content_data, + session->parent_resource_id, + session->title, + false); // shared_with_me + if (!new_entry) { + completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + completion_callback.Run(HTTP_CREATED, make_scoped_ptr( + new FileResource(*new_entry->change_resource.file()))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); + } + + EntryInfo* entry = FindEntryByResourceId(session->resource_id); + if (!entry) { + completion_callback.Run(HTTP_NOT_FOUND, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + ChangeResource* change = &entry->change_resource; + FileResource* file = change->mutable_file(); + if (file->etag().empty() || session->etag != file->etag()) { + completion_callback.Run(HTTP_PRECONDITION, scoped_ptr<FileResource>()); + return CancelCallback(); + } + + file->set_md5_checksum(base::MD5String(content_data)); + entry->content_data = content_data; + file->set_file_size(end_position); + AddNewChangestamp(change); + UpdateETag(file); + + completion_callback.Run(HTTP_SUCCESS, make_scoped_ptr( + new FileResource(*file))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +CancelCallback FakeDriveService::MultipartUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const base::FilePath& local_file_path, + const UploadNewFileOptions& options, + const FileResourceCallback& callback, + const ProgressCallback& progress_callback) { + CallResumeUpload* const call_resume_upload = new CallResumeUpload(); + call_resume_upload->service = weak_ptr_factory_.GetWeakPtr(); + call_resume_upload->content_type = content_type; + call_resume_upload->content_length = content_length; + call_resume_upload->local_file_path = local_file_path; + call_resume_upload->callback = callback; + call_resume_upload->progress_callback = progress_callback; + InitiateUploadNewFile( + content_type, + content_length, + parent_resource_id, + title, + options, + base::Bind(&CallResumeUpload::Run, base::Owned(call_resume_upload))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::MultipartUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const base::FilePath& local_file_path, + const UploadExistingFileOptions& options, + const FileResourceCallback& callback, + const ProgressCallback& progress_callback) { + CallResumeUpload* const call_resume_upload = new CallResumeUpload(); + call_resume_upload->service = weak_ptr_factory_.GetWeakPtr(); + call_resume_upload->content_type = content_type; + call_resume_upload->content_length = content_length; + call_resume_upload->local_file_path = local_file_path; + call_resume_upload->callback = callback; + call_resume_upload->progress_callback = progress_callback; + InitiateUploadExistingFile( + content_type, + content_length, + resource_id, + options, + base::Bind(&CallResumeUpload::Run, base::Owned(call_resume_upload))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const AuthorizeAppCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (entries_.count(resource_id) == 0) { + callback.Run(google_apis::HTTP_NOT_FOUND, GURL()); + return CancelCallback(); + } + + callback.Run(HTTP_SUCCESS, + GURL(base::StringPrintf(open_url_format_.c_str(), + resource_id.c_str(), + app_id.c_str()))); + return CancelCallback(); +} + +CancelCallback FakeDriveService::UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, google_apis::DRIVE_NO_CONNECTION)); + return CancelCallback(); + } + + // Find app_id from app_info_value_ and delete. + base::ListValue* items = NULL; + if (!app_info_value_->GetList("items", &items)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, google_apis::HTTP_NOT_FOUND)); + return CancelCallback(); + } + + for (size_t i = 0; i < items->GetSize(); ++i) { + base::DictionaryValue* item = NULL; + std::string id; + if (items->GetDictionary(i, &item) && item->GetString("id", &id) && + id == app_id) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + items->Remove(i, NULL) ? google_apis::HTTP_NO_CONTENT + : google_apis::HTTP_NOT_FOUND)); + return CancelCallback(); + } + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, google_apis::HTTP_NOT_FOUND)); + return CancelCallback(); +} + +void FakeDriveService::AddNewFile(const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me, + const FileResourceCallback& callback) { + AddNewFileWithResourceId("", content_type, content_data, parent_resource_id, + title, shared_with_me, callback); +} + +void FakeDriveService::AddNewFileWithResourceId( + const std::string& resource_id, + const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return; + } + + const EntryInfo* new_entry = AddNewEntry(resource_id, + content_type, + content_data, + parent_resource_id, + title, + shared_with_me); + if (!new_entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return; + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_CREATED, + base::Passed(make_scoped_ptr( + new FileResource(*new_entry->change_resource.file()))))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); +} + +CancelCallback FakeDriveService::AddNewDirectoryWithResourceId( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + const EntryInfo* new_entry = AddNewEntry(resource_id, + util::kDriveFolderMimeType, + "", // content_data + parent_resource_id, + directory_title, + false); // shared_with_me + if (!new_entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return CancelCallback(); + } + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_CREATED, + base::Passed(make_scoped_ptr( + new FileResource(*new_entry->change_resource.file()))))); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(&FakeDriveService::NotifyObservers, + weak_ptr_factory_.GetWeakPtr())); + return CancelCallback(); +} + +void FakeDriveService::SetLastModifiedTime( + const std::string& resource_id, + const base::Time& last_modified_time, + const FileResourceCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<FileResource>()))); + return; + } + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_NOT_FOUND, + base::Passed(scoped_ptr<FileResource>()))); + return; + } + + ChangeResource* change = &entry->change_resource; + FileResource* file = change->mutable_file(); + file->set_modified_date(last_modified_time); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, + base::Passed(make_scoped_ptr(new FileResource(*file))))); +} + +google_apis::DriveApiErrorCode FakeDriveService::SetUserPermission( + const std::string& resource_id, + google_apis::drive::PermissionRole user_permission) { + DCHECK(thread_checker_.CalledOnValidThread()); + + EntryInfo* entry = FindEntryByResourceId(resource_id); + if (!entry) + return HTTP_NOT_FOUND; + + entry->user_permission = user_permission; + return HTTP_SUCCESS; +} + +void FakeDriveService::AddChangeObserver(ChangeObserver* change_observer) { + change_observers_.AddObserver(change_observer); +} + +void FakeDriveService::RemoveChangeObserver(ChangeObserver* change_observer) { + change_observers_.RemoveObserver(change_observer); +} + +FakeDriveService::EntryInfo* FakeDriveService::FindEntryByResourceId( + const std::string& resource_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + + EntryInfoMap::iterator it = entries_.find(resource_id); + // Deleted entries don't have FileResource. + return it != entries_.end() && it->second->change_resource.file() ? + it->second : NULL; +} + +std::string FakeDriveService::GetNewResourceId() { + DCHECK(thread_checker_.CalledOnValidThread()); + + ++resource_id_count_; + return base::StringPrintf("resource_id_%d", resource_id_count_); +} + +void FakeDriveService::UpdateETag(google_apis::FileResource* file) { + file->set_etag( + "etag_" + base::Int64ToString(about_resource_->largest_change_id())); +} + +void FakeDriveService::AddNewChangestamp(google_apis::ChangeResource* change) { + about_resource_->set_largest_change_id( + about_resource_->largest_change_id() + 1); + change->set_change_id(about_resource_->largest_change_id()); +} + +const FakeDriveService::EntryInfo* FakeDriveService::AddNewEntry( + const std::string& given_resource_id, + const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!parent_resource_id.empty() && + parent_resource_id != GetRootResourceId() && + !entries_.count(parent_resource_id)) { + return NULL; + } + + const std::string resource_id = + given_resource_id.empty() ? GetNewResourceId() : given_resource_id; + if (entries_.count(resource_id)) + return NULL; + GURL upload_url = GURL("https://xxx/upload/" + resource_id); + + scoped_ptr<EntryInfo> new_entry(new EntryInfo); + ChangeResource* new_change = &new_entry->change_resource; + FileResource* new_file = new FileResource; + new_change->set_file(make_scoped_ptr(new_file)); + + // Set the resource ID and the title + new_change->set_file_id(resource_id); + new_file->set_file_id(resource_id); + new_file->set_title(title); + // Set the contents, size and MD5 for a file. + if (content_type != util::kDriveFolderMimeType && + !util::IsKnownHostedDocumentMimeType(content_type)) { + new_entry->content_data = content_data; + new_file->set_file_size(content_data.size()); + new_file->set_md5_checksum(base::MD5String(content_data)); + } + + if (shared_with_me) { + // Set current time to mark the file as shared_with_me. + new_file->set_shared_with_me_date(base::Time::Now()); + } + + std::string escaped_resource_id = net::EscapePath(resource_id); + + // Set mime type. + new_file->set_mime_type(content_type); + + // Set alternate link if needed. + if (content_type == util::kGoogleDocumentMimeType) + new_file->set_alternate_link(GURL("https://document_alternate_link")); + + // Set parents. + if (!parent_resource_id.empty()) { + ParentReference parent; + parent.set_file_id(parent_resource_id); + parent.set_parent_link(GetFakeLinkUrl(parent.file_id())); + std::vector<ParentReference> parents; + parents.push_back(parent); + *new_file->mutable_parents() = parents; + } + + new_entry->share_url = net::AppendOrReplaceQueryParameter( + share_url_base_, "name", title); + + AddNewChangestamp(new_change); + UpdateETag(new_file); + + base::Time published_date = + base::Time() + base::TimeDelta::FromMilliseconds(++published_date_seq_); + new_file->set_created_date(published_date); + + EntryInfo* raw_new_entry = new_entry.release(); + entries_[resource_id] = raw_new_entry; + return raw_new_entry; +} + +void FakeDriveService::GetChangeListInternal( + int64 start_changestamp, + const std::string& search_query, + const std::string& directory_resource_id, + int start_offset, + int max_results, + int* load_counter, + const ChangeListCallback& callback) { + if (offline_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, + DRIVE_NO_CONNECTION, + base::Passed(scoped_ptr<ChangeList>()))); + return; + } + + // Filter out entries per parameters like |directory_resource_id| and + // |search_query|. + ScopedVector<ChangeResource> entries; + int num_entries_matched = 0; + for (EntryInfoMap::iterator it = entries_.begin(); it != entries_.end(); + ++it) { + const ChangeResource& entry = it->second->change_resource; + bool should_exclude = false; + + // If |directory_resource_id| is set, exclude the entry if it's not in + // the target directory. + if (!directory_resource_id.empty()) { + // Get the parent resource ID of the entry. + std::string parent_resource_id; + if (entry.file() && !entry.file()->parents().empty()) + parent_resource_id = entry.file()->parents()[0].file_id(); + + if (directory_resource_id != parent_resource_id) + should_exclude = true; + } + + // If |search_query| is set, exclude the entry if it does not contain the + // search query in the title. + if (!should_exclude && !search_query.empty() && + !EntryMatchWithQuery(entry, search_query)) { + should_exclude = true; + } + + // If |start_changestamp| is set, exclude the entry if the + // changestamp is older than |largest_changestamp|. + // See https://developers.google.com/google-apps/documents-list/ + // #retrieving_all_changes_since_a_given_changestamp + if (start_changestamp > 0 && entry.change_id() < start_changestamp) + should_exclude = true; + + // If the caller requests other list than change list by specifying + // zero-|start_changestamp|, exclude deleted entry from the result. + const bool deleted = entry.is_deleted() || + (entry.file() && entry.file()->labels().is_trashed()); + if (!start_changestamp && deleted) + should_exclude = true; + + // The entry matched the criteria for inclusion. + if (!should_exclude) + ++num_entries_matched; + + // If |start_offset| is set, exclude the entry if the entry is before the + // start index. <= instead of < as |num_entries_matched| was + // already incremented. + if (start_offset > 0 && num_entries_matched <= start_offset) + should_exclude = true; + + if (!should_exclude) { + scoped_ptr<ChangeResource> entry_copied(new ChangeResource); + entry_copied->set_change_id(entry.change_id()); + entry_copied->set_file_id(entry.file_id()); + entry_copied->set_deleted(entry.is_deleted()); + if (entry.file()) { + entry_copied->set_file( + make_scoped_ptr(new FileResource(*entry.file()))); + } + entry_copied->set_modification_date(entry.modification_date()); + entries.push_back(entry_copied.release()); + } + } + + scoped_ptr<ChangeList> change_list(new ChangeList); + if (start_changestamp > 0 && start_offset == 0) { + change_list->set_largest_change_id(about_resource_->largest_change_id()); + } + + // If |max_results| is set, trim the entries if the number exceeded the max + // results. + if (max_results > 0 && entries.size() > static_cast<size_t>(max_results)) { + entries.erase(entries.begin() + max_results, entries.end()); + // Adds the next URL. + // Here, we embed information which is needed for continuing the + // GetChangeList request in the next invocation into url query + // parameters. + GURL next_url(base::StringPrintf( + "http://localhost/?start-offset=%d&max-results=%d", + start_offset + max_results, + max_results)); + if (start_changestamp > 0) { + next_url = net::AppendOrReplaceQueryParameter( + next_url, "changestamp", + base::Int64ToString(start_changestamp).c_str()); + } + if (!search_query.empty()) { + next_url = net::AppendOrReplaceQueryParameter( + next_url, "q", search_query); + } + if (!directory_resource_id.empty()) { + next_url = net::AppendOrReplaceQueryParameter( + next_url, "parent", directory_resource_id); + } + + change_list->set_next_link(next_url); + } + *change_list->mutable_items() = entries.Pass(); + + if (load_counter) + *load_counter += 1; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::Bind(callback, HTTP_SUCCESS, base::Passed(&change_list))); +} + +GURL FakeDriveService::GetNewUploadSessionUrl() { + return GURL("https://upload_session_url/" + + base::Int64ToString(next_upload_sequence_number_++)); +} + +google_apis::CancelCallback FakeDriveService::AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!callback.is_null()); + + NOTREACHED(); + return CancelCallback(); +} + +scoped_ptr<BatchRequestConfiguratorInterface> +FakeDriveService::StartBatchRequest() { + DCHECK(thread_checker_.CalledOnValidThread()); + + NOTREACHED(); + return scoped_ptr<BatchRequestConfiguratorInterface>(); +} + +void FakeDriveService::NotifyObservers() { + FOR_EACH_OBSERVER(ChangeObserver, change_observers_, OnNewChangeAvailable()); +} + +} // namespace drive diff --git a/components/drive/service/fake_drive_service.h b/components/drive/service/fake_drive_service.h new file mode 100644 index 0000000..09ae14a --- /dev/null +++ b/components/drive/service/fake_drive_service.h @@ -0,0 +1,406 @@ +// 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. + +#ifndef COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_ +#define COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "base/threading/thread_checker.h" +#include "components/drive/service/drive_service_interface.h" + +namespace base { +class DictionaryValue; +} + +namespace google_apis { +class AboutResource; +class ChangeResource; +class FileResource; +} + +namespace drive { + +// This class implements a fake DriveService which acts like a real Drive +// service. The fake service works as follows: +// +// 1) Load JSON files and construct the in-memory resource list. +// 2) Return valid responses based on the the in-memory resource list. +// 3) Update the in-memory resource list by requests like DeleteResource(). +class FakeDriveService : public DriveServiceInterface { + public: + class ChangeObserver { + public: + virtual ~ChangeObserver() {} + virtual void OnNewChangeAvailable() = 0; + }; + + FakeDriveService(); + ~FakeDriveService() override; + + // Loads the app list for Drive API. Returns true on success. + bool LoadAppListForDriveApi(const std::string& relative_path); + + // Adds an app to app list. + void AddApp(const std::string& app_id, + const std::string& app_name, + const std::string& product_id, + const std::string& create_url, + bool is_removable); + + // Removes an app by product id. + void RemoveAppByProductId(const std::string& product_id); + + // Returns true if the service knows the given drive app id. + bool HasApp(const std::string& app_id) const; + + // Changes the offline state. All functions fail with DRIVE_NO_CONNECTION + // when offline. By default the offline state is false. + void set_offline(bool offline) { offline_ = offline; } + + // GetAllFileList never returns result when this is set to true. + // Used to emulate the real server's slowness. + void set_never_return_all_file_list(bool value) { + never_return_all_file_list_ = value; + } + + // Changes the default max results returned from GetAllFileList(). + // By default, it's set to 0, which is unlimited. + void set_default_max_results(int default_max_results) { + default_max_results_ = default_max_results; + } + + // Sets the url to the test server to be used as a base for generated share + // urls to the share dialog. + void set_share_url_base(const GURL& share_url_base) { + share_url_base_ = share_url_base; + } + + // Changes the quota fields returned from GetAboutResource(). + void SetQuotaValue(int64 used, int64 total); + + // Returns the AboutResource. + const google_apis::AboutResource& about_resource() const { + return *about_resource_; + } + + // Returns the number of times the file list is successfully loaded by + // GetAllFileList(). + int file_list_load_count() const { return file_list_load_count_; } + + // Returns the number of times the resource list is successfully loaded by + // GetChangeList(). + int change_list_load_count() const { return change_list_load_count_; } + + // Returns the number of times the resource list is successfully loaded by + // GetFileListInDirectory(). + int directory_load_count() const { return directory_load_count_; } + + // Returns the number of times the about resource is successfully loaded + // by GetAboutResource(). + int about_resource_load_count() const { + return about_resource_load_count_; + } + + // Returns the number of times the app list is successfully loaded by + // GetAppList(). + int app_list_load_count() const { return app_list_load_count_; } + + // Returns the number of times GetAllFileList are blocked due to + // set_never_return_all_file_list(). + int blocked_file_list_load_count() const { + return blocked_file_list_load_count_; + } + + // Returns the file path whose request is cancelled just before this method + // invocation. + const base::FilePath& last_cancelled_file() const { + return last_cancelled_file_; + } + + // Returns the (fake) URL for the link. + static GURL GetFakeLinkUrl(const std::string& resource_id); + + // Sets the printf format for constructing the response of AuthorizeApp(). + // The format string must include two %s that are to be filled with + // resource_id and app_id. + void set_open_url_format(const std::string& url_format) { + open_url_format_ = url_format; + } + + // DriveServiceInterface Overrides + void Initialize(const std::string& account_id) override; + void AddObserver(DriveServiceObserver* observer) override; + void RemoveObserver(DriveServiceObserver* observer) override; + bool CanSendRequest() const override; + std::string GetRootResourceId() const override; + bool HasAccessToken() const override; + void RequestAccessToken( + const google_apis::AuthStatusCallback& callback) override; + bool HasRefreshToken() const override; + void ClearAccessToken() override; + void ClearRefreshToken() override; + google_apis::CancelCallback GetAllFileList( + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileListInDirectory( + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + // See the comment for EntryMatchWidthQuery() in .cc file for details about + // the supported search query types. + google_apis::CancelCallback Search( + const std::string& search_query, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback SearchByTitle( + const std::string& title, + const std::string& directory_resource_id, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetChangeList( + int64 start_changestamp, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingChangeList( + const GURL& next_link, + const google_apis::ChangeListCallback& callback) override; + google_apis::CancelCallback GetRemainingFileList( + const GURL& next_link, + const google_apis::FileListCallback& callback) override; + google_apis::CancelCallback GetFileResource( + const std::string& resource_id, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback GetShareUrl( + const std::string& resource_id, + const GURL& embed_origin, + const google_apis::GetShareUrlCallback& callback) override; + google_apis::CancelCallback GetAboutResource( + const google_apis::AboutResourceCallback& callback) override; + google_apis::CancelCallback GetAppList( + const google_apis::AppListCallback& callback) override; + google_apis::CancelCallback DeleteResource( + const std::string& resource_id, + const std::string& etag, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback TrashResource( + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback DownloadFile( + const base::FilePath& local_cache_path, + const std::string& resource_id, + const google_apis::DownloadActionCallback& download_action_callback, + const google_apis::GetContentCallback& get_content_callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback CopyResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback UpdateResource( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& new_title, + const base::Time& last_modified, + const base::Time& last_viewed_by_me, + const google_apis::drive::Properties& properties, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback AddResourceToDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback RemoveResourceFromDirectory( + const std::string& parent_resource_id, + const std::string& resource_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddNewDirectory( + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const google_apis::FileResourceCallback& callback) override; + google_apis::CancelCallback InitiateUploadNewFile( + const std::string& content_type, + int64 content_length, + const std::string& parent_resource_id, + const std::string& title, + const UploadNewFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback InitiateUploadExistingFile( + const std::string& content_type, + int64 content_length, + const std::string& resource_id, + const UploadExistingFileOptions& options, + const google_apis::InitiateUploadCallback& callback) override; + google_apis::CancelCallback ResumeUpload( + const GURL& upload_url, + int64 start_position, + int64 end_position, + int64 content_length, + const std::string& content_type, + const base::FilePath& local_file_path, + const google_apis::drive::UploadRangeCallback& callback, + const google_apis::ProgressCallback& progress_callback) override; + google_apis::CancelCallback GetUploadStatus( + const GURL& upload_url, + int64 content_length, + const google_apis::drive::UploadRangeCallback& callback) override; + google_apis::CancelCallback MultipartUploadNewFile( + const std::string& content_type, + int64 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; + google_apis::CancelCallback MultipartUploadExistingFile( + const std::string& content_type, + int64 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; + google_apis::CancelCallback AuthorizeApp( + const std::string& resource_id, + const std::string& app_id, + const google_apis::AuthorizeAppCallback& callback) override; + google_apis::CancelCallback UninstallApp( + const std::string& app_id, + const google_apis::EntryActionCallback& callback) override; + google_apis::CancelCallback AddPermission( + const std::string& resource_id, + const std::string& email, + google_apis::drive::PermissionRole role, + const google_apis::EntryActionCallback& callback) override; + scoped_ptr<BatchRequestConfiguratorInterface> StartBatchRequest() override; + + // Adds a new file with the given parameters. On success, returns + // HTTP_CREATED with the parsed entry. + // |callback| must not be null. + void AddNewFile(const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me, + const google_apis::FileResourceCallback& callback); + + // Adds a new file with the given |resource_id|. If the id already exists, + // it's an error. This is used for testing cross profile file sharing that + // needs to have matching resource IDs in different fake service instances. + // |callback| must not be null. + void AddNewFileWithResourceId( + const std::string& resource_id, + const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me, + const google_apis::FileResourceCallback& callback); + + // Adds a new directory with the given |resource_id|. + // |callback| must not be null. + google_apis::CancelCallback AddNewDirectoryWithResourceId( + const std::string& resource_id, + const std::string& parent_resource_id, + const std::string& directory_title, + const AddNewDirectoryOptions& options, + const google_apis::FileResourceCallback& callback); + + // Sets the last modified time for an entry specified by |resource_id|. + // On success, returns HTTP_SUCCESS with the parsed entry. + // |callback| must not be null. + void SetLastModifiedTime( + const std::string& resource_id, + const base::Time& last_modified_time, + const google_apis::FileResourceCallback& callback); + + // Sets the user's permission for an entry specified by |resource_id|. + google_apis::DriveApiErrorCode SetUserPermission( + const std::string& resource_id, + google_apis::drive::PermissionRole user_permission); + + void AddChangeObserver(ChangeObserver* observer); + void RemoveChangeObserver(ChangeObserver* observer); + + private: + struct EntryInfo; + struct UploadSession; + + // Returns a pointer to the entry that matches |resource_id|, or NULL if + // not found. + EntryInfo* FindEntryByResourceId(const std::string& resource_id); + + // Returns a new resource ID, which looks like "resource_id_<num>" where + // <num> is a monotonically increasing number starting from 1. + std::string GetNewResourceId(); + + // Increments |largest_changestamp_| and adds the new changestamp. + void AddNewChangestamp(google_apis::ChangeResource* change); + + // Update ETag of |file| based on |largest_changestamp_|. + void UpdateETag(google_apis::FileResource* file); + + // Adds a new entry based on the given parameters. + // |resource_id| can be empty, in the case, the id is automatically generated. + // Returns a pointer to the newly added entry, or NULL if failed. + const EntryInfo* AddNewEntry( + const std::string& resource_id, + const std::string& content_type, + const std::string& content_data, + const std::string& parent_resource_id, + const std::string& title, + bool shared_with_me); + + // Core implementation of GetChangeList. + // This method returns the slice of the all matched entries, and its range + // is between |start_offset| (inclusive) and |start_offset| + |max_results| + // (exclusive). + // Increments *load_counter by 1 before it returns successfully. + void GetChangeListInternal( + int64 start_changestamp, + const std::string& search_query, + const std::string& directory_resource_id, + int start_offset, + int max_results, + int* load_counter, + const google_apis::ChangeListCallback& callback); + + // Returns new upload session URL. + GURL GetNewUploadSessionUrl(); + + void NotifyObservers(); + + // The class is expected to run on UI thread. + base::ThreadChecker thread_checker_; + + typedef std::map<std::string, EntryInfo*> EntryInfoMap; + EntryInfoMap entries_; + scoped_ptr<google_apis::AboutResource> about_resource_; + scoped_ptr<base::DictionaryValue> app_info_value_; + std::map<GURL, UploadSession> upload_sessions_; + int64 published_date_seq_; + int64 next_upload_sequence_number_; + int default_max_results_; + int resource_id_count_; + int file_list_load_count_; + int change_list_load_count_; + int directory_load_count_; + int about_resource_load_count_; + int app_list_load_count_; + int blocked_file_list_load_count_; + bool offline_; + bool never_return_all_file_list_; + base::FilePath last_cancelled_file_; + GURL share_url_base_; + std::string app_json_template_; + std::string open_url_format_; + + base::ObserverList<ChangeObserver> change_observers_; + + base::WeakPtrFactory<FakeDriveService> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(FakeDriveService); +}; + +} // namespace drive + +#endif // COMPONENTS_DRIVE_SERVICE_FAKE_DRIVE_SERVICE_H_ diff --git a/components/drive/service/fake_drive_service_unittest.cc b/components/drive/service/fake_drive_service_unittest.cc new file mode 100644 index 0000000..009f8ca --- /dev/null +++ b/components/drive/service/fake_drive_service_unittest.cc @@ -0,0 +1,2177 @@ +// 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/service/fake_drive_service.h" + +#include <string> +#include <vector> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/md5.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/stl_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "components/drive/service/test_util.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::AboutResource; +using google_apis::AppList; +using google_apis::ChangeList; +using google_apis::ChangeResource; +using google_apis::FileList; +using google_apis::FileResource; +using google_apis::DRIVE_NO_CONNECTION; +using google_apis::DRIVE_OTHER_ERROR; +using google_apis::DriveApiErrorCode; +using google_apis::GetContentCallback; +using google_apis::HTTP_CREATED; +using google_apis::HTTP_FORBIDDEN; +using google_apis::HTTP_NOT_FOUND; +using google_apis::HTTP_NO_CONTENT; +using google_apis::HTTP_PRECONDITION; +using google_apis::HTTP_RESUME_INCOMPLETE; +using google_apis::HTTP_SUCCESS; +using google_apis::ProgressCallback; +using google_apis::UploadRangeResponse; + +namespace drive { + +namespace test_util { + +using google_apis::test_util::AppendProgressCallbackResult; +using google_apis::test_util::CreateCopyResultCallback; +using google_apis::test_util::ProgressInfo; +using google_apis::test_util::TestGetContentCallback; +using google_apis::test_util::WriteStringToFile; + +} // namespace test_util + +namespace { + +class FakeDriveServiceTest : public testing::Test { + protected: + // Returns the resource entry that matches |resource_id|. + scoped_ptr<FileResource> FindEntry(const std::string& resource_id) { + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.GetFileResource( + resource_id, test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + return entry.Pass(); + } + + // Returns true if the resource identified by |resource_id| exists. + bool Exists(const std::string& resource_id) { + scoped_ptr<FileResource> entry = FindEntry(resource_id); + return entry && !entry->labels().is_trashed(); + } + + // Adds a new directory at |parent_resource_id| with the given name. + // Returns true on success. + bool AddNewDirectory(const std::string& parent_resource_id, + const std::string& directory_title) { + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + parent_resource_id, directory_title, AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + return error == HTTP_CREATED; + } + + // Returns true if the resource identified by |resource_id| has a parent + // identified by |parent_id|. + bool HasParent(const std::string& resource_id, const std::string& parent_id) { + scoped_ptr<FileResource> entry = FindEntry(resource_id); + if (entry) { + for (size_t i = 0; i < entry->parents().size(); ++i) { + if (entry->parents()[i].file_id() == parent_id) + return true; + } + } + return false; + } + + int64 GetLargestChangeByAboutResource() { + DriveApiErrorCode error; + scoped_ptr<AboutResource> about_resource; + fake_service_.GetAboutResource( + test_util::CreateCopyResultCallback(&error, &about_resource)); + base::RunLoop().RunUntilIdle(); + return about_resource->largest_change_id(); + } + + base::MessageLoop message_loop_; + FakeDriveService fake_service_; +}; + +TEST_F(FakeDriveServiceTest, GetAllFileList) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetAllFileList( + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. + EXPECT_EQ(15U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.file_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetAllFileList_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetAllFileList( + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(file_list); +} + +TEST_F(FakeDriveServiceTest, GetFileListInDirectory_InRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetFileListInDirectory( + fake_service_.GetRootResourceId(), + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 8 entries in the root directory. + EXPECT_EQ(8U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.directory_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetFileListInDirectory_InNonRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetFileListInDirectory( + "1_folder_resource_id", + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There is three entries in 1_folder_resource_id + // directory. + EXPECT_EQ(3U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.directory_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetFileListInDirectory_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetFileListInDirectory( + fake_service_.GetRootResourceId(), + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(file_list); +} + +TEST_F(FakeDriveServiceTest, Search) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "File", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 4 entries that contain "File" in their + // titles. + EXPECT_EQ(4U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, Search_WithAttribute) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "title:1.txt", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 4 entries that contain "1.txt" in their + // titles. + EXPECT_EQ(4U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, Search_MultipleQueries) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "Directory 1", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // There are 2 entries that contain both "Directory" and "1" in their titles. + EXPECT_EQ(2U, file_list->items().size()); + + fake_service_.Search( + "\"Directory 1\"", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // There is 1 entry that contain "Directory 1" in its title. + EXPECT_EQ(1U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, Search_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "Directory 1", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(file_list); +} + +TEST_F(FakeDriveServiceTest, Search_Deleted) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_NO_CONTENT, error); + + error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "File", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 4 entries that contain "File" in their + // titles and one of them is deleted. + EXPECT_EQ(3U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, Search_Trashed) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_SUCCESS, error); + + error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "File", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 4 entries that contain "File" in their + // titles and one of them is deleted. + EXPECT_EQ(3U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, SearchByTitle) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.SearchByTitle( + "1.txt", // title + fake_service_.GetRootResourceId(), // directory_resource_id + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 2 entries that contain "1.txt" in their + // titles directly under the root directory. + EXPECT_EQ(2U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, SearchByTitle_EmptyDirectoryResourceId) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.SearchByTitle( + "1.txt", // title + "", // directory resource id + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + // Do some sanity check. There are 4 entries that contain "1.txt" in their + // titles. + EXPECT_EQ(4U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, SearchByTitle_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.SearchByTitle( + "Directory 1", // title + fake_service_.GetRootResourceId(), // directory_resource_id + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(file_list); +} + +TEST_F(FakeDriveServiceTest, GetChangeList_NoNewEntries) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + fake_service_.about_resource().largest_change_id() + 1, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + EXPECT_EQ(fake_service_.about_resource().largest_change_id(), + change_list->largest_change_id()); + // This should be empty as the latest changestamp was passed to + // GetChangeList(), hence there should be no new entries. + EXPECT_EQ(0U, change_list->items().size()); + // It's considered loaded even if the result is empty. + EXPECT_EQ(1, fake_service_.change_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetChangeList_WithNewEntry) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + const int64 old_largest_change_id = + fake_service_.about_resource().largest_change_id(); + + // Add a new directory in the root directory. + ASSERT_TRUE(AddNewDirectory( + fake_service_.GetRootResourceId(), "new directory")); + + // Get the resource list newer than old_largest_change_id. + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + old_largest_change_id + 1, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + EXPECT_EQ(fake_service_.about_resource().largest_change_id(), + change_list->largest_change_id()); + // The result should only contain the newly created directory. + ASSERT_EQ(1U, change_list->items().size()); + ASSERT_TRUE(change_list->items()[0]->file()); + EXPECT_EQ("new directory", change_list->items()[0]->file()->title()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetChangeList_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + 654321, // start_changestamp + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(change_list); +} + +TEST_F(FakeDriveServiceTest, GetChangeList_DeletedEntry) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + ASSERT_TRUE(Exists("2_file_resource_id")); + const int64 old_largest_change_id = + fake_service_.about_resource().largest_change_id(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(HTTP_NO_CONTENT, error); + ASSERT_FALSE(Exists("2_file_resource_id")); + + // Get the resource list newer than old_largest_change_id. + error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + old_largest_change_id + 1, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + EXPECT_EQ(fake_service_.about_resource().largest_change_id(), + change_list->largest_change_id()); + // The result should only contain the deleted file. + ASSERT_EQ(1U, change_list->items().size()); + const ChangeResource& item = *change_list->items()[0]; + EXPECT_EQ("2_file_resource_id", item.file_id()); + EXPECT_FALSE(item.file()); + EXPECT_TRUE(item.is_deleted()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetChangeList_TrashedEntry) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + ASSERT_TRUE(Exists("2_file_resource_id")); + const int64 old_largest_change_id = + fake_service_.about_resource().largest_change_id(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + ASSERT_EQ(HTTP_SUCCESS, error); + ASSERT_FALSE(Exists("2_file_resource_id")); + + // Get the resource list newer than old_largest_change_id. + error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + old_largest_change_id + 1, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + EXPECT_EQ(fake_service_.about_resource().largest_change_id(), + change_list->largest_change_id()); + // The result should only contain the trashed file. + ASSERT_EQ(1U, change_list->items().size()); + const ChangeResource& item = *change_list->items()[0]; + EXPECT_EQ("2_file_resource_id", item.file_id()); + ASSERT_TRUE(item.file()); + EXPECT_TRUE(item.file()->labels().is_trashed()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetRemainingFileList_GetAllFileList) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_default_max_results(6); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetAllFileList( + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + // Do some sanity check. + // The number of results is 14 entries. Thus, it should split into three + // chunks: 6, 6, and then 2. + EXPECT_EQ(6U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.file_list_load_count()); + + // Second page loading. + // Keep the next url before releasing the |file_list|. + GURL next_url(file_list->next_link()); + + error = DRIVE_OTHER_ERROR; + file_list.reset(); + fake_service_.GetRemainingFileList( + next_url, + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + EXPECT_EQ(6U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.file_list_load_count()); + + // Third page loading. + next_url = file_list->next_link(); + + error = DRIVE_OTHER_ERROR; + file_list.reset(); + fake_service_.GetRemainingFileList( + next_url, + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + EXPECT_EQ(3U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.file_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetRemainingFileList_GetFileListInDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_default_max_results(3); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.GetFileListInDirectory( + fake_service_.GetRootResourceId(), + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + // Do some sanity check. + // The number of results is 8 entries. Thus, it should split into three + // chunks: 3, 3, and then 2. + EXPECT_EQ(3U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.directory_load_count()); + + // Second page loading. + // Keep the next url before releasing the |file_list|. + GURL next_url = file_list->next_link(); + + error = DRIVE_OTHER_ERROR; + file_list.reset(); + fake_service_.GetRemainingFileList( + next_url, + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + EXPECT_EQ(3U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.directory_load_count()); + + // Third page loading. + next_url = file_list->next_link(); + + error = DRIVE_OTHER_ERROR; + file_list.reset(); + fake_service_.GetRemainingFileList( + next_url, + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + EXPECT_EQ(2U, file_list->items().size()); + EXPECT_EQ(1, fake_service_.directory_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetRemainingFileList_Search) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_default_max_results(2); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileList> file_list; + fake_service_.Search( + "File", // search_query + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + // Do some sanity check. + // The number of results is 4 entries. Thus, it should split into two + // chunks: 2, and then 2 + EXPECT_EQ(2U, file_list->items().size()); + + // Second page loading. + // Keep the next url before releasing the |file_list|. + GURL next_url = file_list->next_link(); + + error = DRIVE_OTHER_ERROR; + file_list.reset(); + fake_service_.GetRemainingFileList( + next_url, + test_util::CreateCopyResultCallback(&error, &file_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(file_list); + + EXPECT_EQ(2U, file_list->items().size()); +} + +TEST_F(FakeDriveServiceTest, GetRemainingChangeList_GetChangeList) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_default_max_results(2); + const int64 old_largest_change_id = + fake_service_.about_resource().largest_change_id(); + + // Add 5 new directory in the root directory. + for (int i = 0; i < 5; ++i) { + ASSERT_TRUE(AddNewDirectory( + fake_service_.GetRootResourceId(), + base::StringPrintf("new directory %d", i))); + } + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<ChangeList> change_list; + fake_service_.GetChangeList( + old_largest_change_id + 1, // start_changestamp + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + + // Do some sanity check. + // The number of results is 5 entries. Thus, it should split into three + // chunks: 2, 2 and then 1. + EXPECT_EQ(2U, change_list->items().size()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); + + // Second page loading. + // Keep the next url before releasing the |change_list|. + GURL next_url = change_list->next_link(); + + error = DRIVE_OTHER_ERROR; + change_list.reset(); + fake_service_.GetRemainingChangeList( + next_url, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + + EXPECT_EQ(2U, change_list->items().size()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); + + // Third page loading. + next_url = change_list->next_link(); + + error = DRIVE_OTHER_ERROR; + change_list.reset(); + fake_service_.GetRemainingChangeList( + next_url, + test_util::CreateCopyResultCallback(&error, &change_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(change_list); + + EXPECT_EQ(1U, change_list->items().size()); + EXPECT_EQ(1, fake_service_.change_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetAboutResource) { + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<AboutResource> about_resource; + fake_service_.GetAboutResource( + test_util::CreateCopyResultCallback(&error, &about_resource)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + + ASSERT_TRUE(about_resource); + // Do some sanity check. + EXPECT_EQ(fake_service_.GetRootResourceId(), + about_resource->root_folder_id()); + EXPECT_EQ(1, fake_service_.about_resource_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetAboutResource_Offline) { + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<AboutResource> about_resource; + fake_service_.GetAboutResource( + test_util::CreateCopyResultCallback(&error, &about_resource)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(about_resource); +} + +TEST_F(FakeDriveServiceTest, GetAppList) { + ASSERT_TRUE(fake_service_.LoadAppListForDriveApi( + "drive/applist.json")); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<AppList> app_list; + fake_service_.GetAppList( + test_util::CreateCopyResultCallback(&error, &app_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + + ASSERT_TRUE(app_list); + EXPECT_EQ(1, fake_service_.app_list_load_count()); +} + +TEST_F(FakeDriveServiceTest, GetAppList_Offline) { + ASSERT_TRUE(fake_service_.LoadAppListForDriveApi( + "drive/applist.json")); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<AppList> app_list; + fake_service_.GetAppList( + test_util::CreateCopyResultCallback(&error, &app_list)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(app_list); +} + +TEST_F(FakeDriveServiceTest, GetFileResource_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.GetFileResource( + kResourceId, test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + // Do some sanity check. + EXPECT_EQ(kResourceId, entry->file_id()); +} + +TEST_F(FakeDriveServiceTest, GetFileResource_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.GetFileResource( + kResourceId, test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + ASSERT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, GetFileResource_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.GetFileResource( + kResourceId, test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, GetShareUrl) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL share_url; + fake_service_.GetShareUrl( + kResourceId, + GURL(), // embed origin + test_util::CreateCopyResultCallback(&error, &share_url)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_FALSE(share_url.is_empty()); +} + +TEST_F(FakeDriveServiceTest, DeleteResource_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + // Resource "2_file_resource_id" should now exist. + ASSERT_TRUE(Exists("2_file_resource_id")); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NO_CONTENT, error); + // Resource "2_file_resource_id" should be gone now. + EXPECT_FALSE(Exists("2_file_resource_id")); + + error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_FALSE(Exists("2_file_resource_id")); +} + +TEST_F(FakeDriveServiceTest, DeleteResource_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("nonexisting_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, DeleteResource_ETagMatch) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + // Resource "2_file_resource_id" should now exist. + scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id"); + ASSERT_TRUE(entry); + ASSERT_FALSE(entry->labels().is_trashed()); + ASSERT_FALSE(entry->etag().empty()); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + entry->etag() + "_mismatch", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_PRECONDITION, error); + // Resource "2_file_resource_id" should still exist. + EXPECT_TRUE(Exists("2_file_resource_id")); + + error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + entry->etag(), + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_NO_CONTENT, error); + // Resource "2_file_resource_id" should be gone now. + EXPECT_FALSE(Exists("2_file_resource_id")); +} + +TEST_F(FakeDriveServiceTest, DeleteResource_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); +} + +TEST_F(FakeDriveServiceTest, DeleteResource_Forbidden) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission( + "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.DeleteResource("2_file_resource_id", + std::string(), // etag + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_FORBIDDEN, error); +} + +TEST_F(FakeDriveServiceTest, TrashResource_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + // Resource "2_file_resource_id" should now exist. + ASSERT_TRUE(Exists("2_file_resource_id")); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + // Resource "2_file_resource_id" should be gone now. + EXPECT_FALSE(Exists("2_file_resource_id")); + + error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_FALSE(Exists("2_file_resource_id")); +} + +TEST_F(FakeDriveServiceTest, TrashResource_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("nonexisting_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, TrashResource_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); +} + +TEST_F(FakeDriveServiceTest, TrashResource_Forbidden) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission( + "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.TrashResource("2_file_resource_id", + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_FORBIDDEN, error); +} + +TEST_F(FakeDriveServiceTest, DownloadFile_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + std::vector<test_util::ProgressInfo> download_progress_values; + + const base::FilePath kOutputFilePath = + temp_dir.path().AppendASCII("whatever.txt"); + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + base::FilePath output_file_path; + test_util::TestGetContentCallback get_content_callback; + fake_service_.DownloadFile( + kOutputFilePath, + "2_file_resource_id", + test_util::CreateCopyResultCallback(&error, &output_file_path), + get_content_callback.callback(), + base::Bind(&test_util::AppendProgressCallbackResult, + &download_progress_values)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_EQ(output_file_path, kOutputFilePath); + std::string content; + ASSERT_TRUE(base::ReadFileToString(output_file_path, &content)); + EXPECT_EQ("This is some test content.", content); + ASSERT_TRUE(!download_progress_values.empty()); + EXPECT_TRUE(base::STLIsSorted(download_progress_values)); + EXPECT_LE(0, download_progress_values.front().first); + EXPECT_GE(26, download_progress_values.back().first); + EXPECT_EQ(content, get_content_callback.GetConcatenatedData()); +} + +TEST_F(FakeDriveServiceTest, DownloadFile_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + const base::FilePath kOutputFilePath = + temp_dir.path().AppendASCII("whatever.txt"); + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + base::FilePath output_file_path; + fake_service_.DownloadFile( + kOutputFilePath, + "non_existent_file_resource_id", + test_util::CreateCopyResultCallback(&error, &output_file_path), + GetContentCallback(), + ProgressCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, DownloadFile_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + const base::FilePath kOutputFilePath = + temp_dir.path().AppendASCII("whatever.txt"); + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + base::FilePath output_file_path; + fake_service_.DownloadFile( + kOutputFilePath, + "2_file_resource_id", + test_util::CreateCopyResultCallback(&error, &output_file_path), + GetContentCallback(), + ProgressCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); +} + +TEST_F(FakeDriveServiceTest, CopyResource) { + const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123}; + + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "2_file_resource_id"; + const std::string kParentResourceId = "2_folder_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.CopyResource( + kResourceId, + kParentResourceId, + "new title", + base::Time::FromUTCExploded(kModifiedDate), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + // The copied entry should have the new resource ID and the title. + EXPECT_NE(kResourceId, entry->file_id()); + EXPECT_EQ("new title", entry->title()); + EXPECT_EQ(base::Time::FromUTCExploded(kModifiedDate), entry->modified_date()); + EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId)); + // Should be incremented as a new hosted document was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, CopyResource_NonExisting) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.CopyResource( + kResourceId, + "1_folder_resource_id", + "new title", + base::Time(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, CopyResource_EmptyParentResourceId) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.CopyResource( + kResourceId, + std::string(), + "new title", + base::Time(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + // The copied entry should have the new resource ID and the title. + EXPECT_NE(kResourceId, entry->file_id()); + EXPECT_EQ("new title", entry->title()); + EXPECT_TRUE(HasParent(kResourceId, fake_service_.GetRootResourceId())); + // Should be incremented as a new hosted document was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, CopyResource_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.CopyResource( + kResourceId, + "1_folder_resource_id", + "new title", + base::Time(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, UpdateResource) { + const base::Time::Exploded kModifiedDate = {2012, 7, 0, 19, 15, 59, 13, 123}; + const base::Time::Exploded kViewedDate = {2013, 8, 1, 20, 16, 00, 14, 234}; + + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "2_file_resource_id"; + const std::string kParentResourceId = "2_folder_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.UpdateResource( + kResourceId, kParentResourceId, "new title", + base::Time::FromUTCExploded(kModifiedDate), + base::Time::FromUTCExploded(kViewedDate), + google_apis::drive::Properties(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + // The updated entry should have the new title. + EXPECT_EQ(kResourceId, entry->file_id()); + EXPECT_EQ("new title", entry->title()); + EXPECT_EQ(base::Time::FromUTCExploded(kModifiedDate), + entry->modified_date()); + EXPECT_EQ(base::Time::FromUTCExploded(kViewedDate), + entry->last_viewed_by_me_date()); + EXPECT_TRUE(HasParent(kResourceId, kParentResourceId)); + // Should be incremented as a new hosted document was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, UpdateResource_NonExisting) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.UpdateResource( + kResourceId, "1_folder_resource_id", "new title", base::Time(), + base::Time(), google_apis::drive::Properties(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, UpdateResource_EmptyParentResourceId) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "2_file_resource_id"; + + // Just make sure that the resource is under root. + ASSERT_TRUE(HasParent(kResourceId, "fake_root")); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.UpdateResource( + kResourceId, std::string(), "new title", base::Time(), base::Time(), + google_apis::drive::Properties(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + // The updated entry should have the new title. + EXPECT_EQ(kResourceId, entry->file_id()); + EXPECT_EQ("new title", entry->title()); + EXPECT_TRUE(HasParent(kResourceId, "fake_root")); + // Should be incremented as a new hosted document was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, UpdateResource_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "2_file_resource_id"; + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.UpdateResource( + kResourceId, std::string(), "new title", base::Time(), base::Time(), + google_apis::drive::Properties(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, UpdateResource_Forbidden) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "2_file_resource_id"; + EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission( + kResourceId, google_apis::drive::PERMISSION_ROLE_READER)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.UpdateResource( + kResourceId, std::string(), "new title", base::Time(), base::Time(), + google_apis::drive::Properties(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_FORBIDDEN, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, AddResourceToDirectory_FileInRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "2_file_resource_id"; + const std::string kOldParentResourceId = fake_service_.GetRootResourceId(); + const std::string kNewParentResourceId = "1_folder_resource_id"; + + // Here's the original parent link. + EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId)); + EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.AddResourceToDirectory( + kNewParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + + // The parent link should now be changed. + EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId)); + EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId)); + // Should be incremented as a file was moved. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddResourceToDirectory_FileInNonRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "subdirectory_file_1_id"; + const std::string kOldParentResourceId = "1_folder_resource_id"; + const std::string kNewParentResourceId = "2_folder_resource_id"; + + // Here's the original parent link. + EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId)); + EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.AddResourceToDirectory( + kNewParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + + // The parent link should now be changed. + EXPECT_TRUE(HasParent(kResourceId, kOldParentResourceId)); + EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId)); + // Should be incremented as a file was moved. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddResourceToDirectory_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_file"; + const std::string kNewParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.AddResourceToDirectory( + kNewParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, AddResourceToDirectory_OrphanFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "1_orphanfile_resource_id"; + const std::string kNewParentResourceId = "1_folder_resource_id"; + + // The file does not belong to any directory, even to the root. + EXPECT_FALSE(HasParent(kResourceId, kNewParentResourceId)); + EXPECT_FALSE(HasParent(kResourceId, fake_service_.GetRootResourceId())); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.AddResourceToDirectory( + kNewParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + + // The parent link should now be changed. + EXPECT_TRUE(HasParent(kResourceId, kNewParentResourceId)); + EXPECT_FALSE(HasParent(kResourceId, fake_service_.GetRootResourceId())); + // Should be incremented as a file was moved. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddResourceToDirectory_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "2_file_resource_id"; + const std::string kNewParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.AddResourceToDirectory( + kNewParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); +} + +TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kResourceId = "subdirectory_file_1_id"; + const std::string kParentResourceId = "1_folder_resource_id"; + + scoped_ptr<FileResource> entry = FindEntry(kResourceId); + ASSERT_TRUE(entry); + // The entry should have a parent now. + ASSERT_FALSE(entry->parents().empty()); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.RemoveResourceFromDirectory( + kParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NO_CONTENT, error); + + entry = FindEntry(kResourceId); + ASSERT_TRUE(entry); + // The entry should have no parent now. + ASSERT_TRUE(entry->parents().empty()); + // Should be incremented as a file was moved to the root directory. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_file"; + const std::string kParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.RemoveResourceFromDirectory( + kParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_OrphanFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "1_orphanfile_resource_id"; + const std::string kParentResourceId = fake_service_.GetRootResourceId(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.RemoveResourceFromDirectory( + kParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); +} + +TEST_F(FakeDriveServiceTest, RemoveResourceFromDirectory_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "subdirectory_file_1_id"; + const std::string kParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + fake_service_.RemoveResourceFromDirectory( + kParentResourceId, + kResourceId, + test_util::CreateCopyResultCallback(&error)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_EmptyParent) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + std::string(), "new directory", AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_TRUE(entry->IsDirectory()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ("new directory", entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_ToRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + fake_service_.GetRootResourceId(), "new directory", + AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_TRUE(entry->IsDirectory()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ("new directory", entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_ToRootDirectoryOnEmptyFileSystem) { + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + fake_service_.GetRootResourceId(), "new directory", + AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_TRUE(entry->IsDirectory()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ("new directory", entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_ToNonRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + kParentResourceId, "new directory", AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_TRUE(entry->IsDirectory()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ("new directory", entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId)); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_ToNonexistingDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kParentResourceId = "nonexisting_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + kParentResourceId, "new directory", AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, AddNewDirectory_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewDirectory( + fake_service_.GetRootResourceId(), "new directory", + AddNewDirectoryOptions(), + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadNewFile_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", 13, "1_folder_resource_id", "new file.foo", + UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadNewFile_NotFound) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", 13, "non_existent", "new file.foo", UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadNewFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", 13, "1_folder_resource_id", "new file.foo", + UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_FALSE(upload_location.is_empty()); + EXPECT_NE(GURL("https://1_folder_resumable_create_media_link?mode=newfile"), + upload_location); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "test/foo", 13, "2_file_resource_id", UploadExistingFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_Forbidden) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + EXPECT_EQ(HTTP_SUCCESS, fake_service_.SetUserPermission( + "2_file_resource_id", google_apis::drive::PERMISSION_ROLE_READER)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "test/foo", 13, "2_file_resource_id", UploadExistingFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_FORBIDDEN, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_NotFound) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "test/foo", 13, "non_existent", UploadExistingFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUploadExistingFile_WrongETag) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + UploadExistingFileOptions options; + options.etag = "invalid_etag"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "text/plain", + 13, + "2_file_resource_id", + options, + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_PRECONDITION, error); + EXPECT_TRUE(upload_location.is_empty()); +} + +TEST_F(FakeDriveServiceTest, InitiateUpload_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id"); + ASSERT_TRUE(entry); + + UploadExistingFileOptions options; + options.etag = entry->etag(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "text/plain", + 13, + "2_file_resource_id", + options, + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_TRUE(upload_location.is_valid()); +} + +TEST_F(FakeDriveServiceTest, ResumeUpload_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", 15, "1_folder_resource_id", "new file.foo", + UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_FALSE(upload_location.is_empty()); + EXPECT_NE(GURL("https://1_folder_resumable_create_media_link"), + upload_location); + + fake_service_.set_offline(true); + + UploadRangeResponse response; + scoped_ptr<FileResource> entry; + fake_service_.ResumeUpload( + upload_location, + 0, 13, 15, "test/foo", + base::FilePath(), + test_util::CreateCopyResultCallback(&response, &entry), + ProgressCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, response.code); + EXPECT_FALSE(entry.get()); +} + +TEST_F(FakeDriveServiceTest, ResumeUpload_NotFound) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", 15, "1_folder_resource_id", "new file.foo", + UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(HTTP_SUCCESS, error); + + UploadRangeResponse response; + scoped_ptr<FileResource> entry; + fake_service_.ResumeUpload( + GURL("https://foo.com/"), + 0, 13, 15, "test/foo", + base::FilePath(), + test_util::CreateCopyResultCallback(&response, &entry), + ProgressCallback()); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, response.code); + EXPECT_FALSE(entry.get()); +} + +TEST_F(FakeDriveServiceTest, ResumeUpload_ExistingFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath local_file_path = + temp_dir.path().Append(FILE_PATH_LITERAL("File 1.txt")); + std::string contents("hogefugapiyo"); + ASSERT_TRUE(test_util::WriteStringToFile(local_file_path, contents)); + + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + scoped_ptr<FileResource> entry = FindEntry("2_file_resource_id"); + ASSERT_TRUE(entry); + + UploadExistingFileOptions options; + options.etag = entry->etag(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadExistingFile( + "text/plain", + contents.size(), + "2_file_resource_id", + options, + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(HTTP_SUCCESS, error); + + UploadRangeResponse response; + entry.reset(); + std::vector<test_util::ProgressInfo> upload_progress_values; + fake_service_.ResumeUpload( + upload_location, + 0, contents.size() / 2, contents.size(), "text/plain", + local_file_path, + test_util::CreateCopyResultCallback(&response, &entry), + base::Bind(&test_util::AppendProgressCallbackResult, + &upload_progress_values)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code); + EXPECT_FALSE(entry.get()); + ASSERT_TRUE(!upload_progress_values.empty()); + EXPECT_TRUE(base::STLIsSorted(upload_progress_values)); + EXPECT_LE(0, upload_progress_values.front().first); + EXPECT_GE(static_cast<int64>(contents.size() / 2), + upload_progress_values.back().first); + + upload_progress_values.clear(); + fake_service_.ResumeUpload( + upload_location, + contents.size() / 2, contents.size(), contents.size(), "text/plain", + local_file_path, + test_util::CreateCopyResultCallback(&response, &entry), + base::Bind(&test_util::AppendProgressCallbackResult, + &upload_progress_values)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, response.code); + EXPECT_TRUE(entry.get()); + EXPECT_EQ(static_cast<int64>(contents.size()), entry->file_size()); + EXPECT_TRUE(Exists(entry->file_id())); + ASSERT_TRUE(!upload_progress_values.empty()); + EXPECT_TRUE(base::STLIsSorted(upload_progress_values)); + EXPECT_LE(0, upload_progress_values.front().first); + EXPECT_GE(static_cast<int64>(contents.size() - contents.size() / 2), + upload_progress_values.back().first); + EXPECT_EQ(base::MD5String(contents), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, ResumeUpload_NewFile) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath local_file_path = + temp_dir.path().Append(FILE_PATH_LITERAL("new file.foo")); + std::string contents("hogefugapiyo"); + ASSERT_TRUE(test_util::WriteStringToFile(local_file_path, contents)); + + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + GURL upload_location; + fake_service_.InitiateUploadNewFile( + "test/foo", contents.size(), "1_folder_resource_id", "new file.foo", + UploadNewFileOptions(), + test_util::CreateCopyResultCallback(&error, &upload_location)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + EXPECT_FALSE(upload_location.is_empty()); + EXPECT_NE(GURL("https://1_folder_resumable_create_media_link"), + upload_location); + + UploadRangeResponse response; + scoped_ptr<FileResource> entry; + std::vector<test_util::ProgressInfo> upload_progress_values; + fake_service_.ResumeUpload( + upload_location, + 0, contents.size() / 2, contents.size(), "test/foo", + local_file_path, + test_util::CreateCopyResultCallback(&response, &entry), + base::Bind(&test_util::AppendProgressCallbackResult, + &upload_progress_values)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code); + EXPECT_FALSE(entry.get()); + ASSERT_TRUE(!upload_progress_values.empty()); + EXPECT_TRUE(base::STLIsSorted(upload_progress_values)); + EXPECT_LE(0, upload_progress_values.front().first); + EXPECT_GE(static_cast<int64>(contents.size() / 2), + upload_progress_values.back().first); + + upload_progress_values.clear(); + fake_service_.ResumeUpload( + upload_location, + contents.size() / 2, contents.size(), contents.size(), "test/foo", + local_file_path, + test_util::CreateCopyResultCallback(&response, &entry), + base::Bind(&test_util::AppendProgressCallbackResult, + &upload_progress_values)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, response.code); + EXPECT_TRUE(entry.get()); + EXPECT_EQ(static_cast<int64>(contents.size()), entry->file_size()); + EXPECT_TRUE(Exists(entry->file_id())); + ASSERT_TRUE(!upload_progress_values.empty()); + EXPECT_TRUE(base::STLIsSorted(upload_progress_values)); + EXPECT_LE(0, upload_progress_values.front().first); + EXPECT_GE(static_cast<int64>(contents.size() - contents.size() / 2), + upload_progress_values.back().first); + EXPECT_EQ(base::MD5String(contents), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_ToRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + fake_service_.GetRootResourceId(), + kTitle, + false, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_EQ(kContentType, entry->mime_type()); + EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ(kTitle, entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); + EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_ToRootDirectoryOnEmptyFileSystem) { + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + fake_service_.GetRootResourceId(), + kTitle, + false, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_EQ(kContentType, entry->mime_type()); + EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ(kTitle, entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); + EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_ToNonRootDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + const std::string kParentResourceId = "1_folder_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + kParentResourceId, + kTitle, + false, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_EQ(kContentType, entry->mime_type()); + EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ(kTitle, entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), kParentResourceId)); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); + EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_ToNonexistingDirectory) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + const std::string kParentResourceId = "nonexisting_resource_id"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + kParentResourceId, + kTitle, + false, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + fake_service_.GetRootResourceId(), + kTitle, + false, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, AddNewFile_SharedWithMeLabel) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kContentType = "text/plain"; + const std::string kContentData = "This is some test content."; + const std::string kTitle = "new file"; + + int64 old_largest_change_id = GetLargestChangeByAboutResource(); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.AddNewFile( + kContentType, + kContentData, + fake_service_.GetRootResourceId(), + kTitle, + true, // shared_with_me + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_CREATED, error); + ASSERT_TRUE(entry); + EXPECT_EQ(kContentType, entry->mime_type()); + EXPECT_EQ(static_cast<int64>(kContentData.size()), entry->file_size()); + EXPECT_EQ("resource_id_1", entry->file_id()); + EXPECT_EQ(kTitle, entry->title()); + EXPECT_TRUE(HasParent(entry->file_id(), fake_service_.GetRootResourceId())); + EXPECT_FALSE(entry->shared_with_me_date().is_null()); + // Should be incremented as a new directory was created. + EXPECT_EQ(old_largest_change_id + 1, + fake_service_.about_resource().largest_change_id()); + EXPECT_EQ(old_largest_change_id + 1, GetLargestChangeByAboutResource()); + EXPECT_EQ(base::MD5String(kContentData), entry->md5_checksum()); +} + +TEST_F(FakeDriveServiceTest, SetLastModifiedTime_ExistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "2_file_resource_id"; + base::Time time; + ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.SetLastModifiedTime( + kResourceId, + time, + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_SUCCESS, error); + ASSERT_TRUE(entry); + EXPECT_EQ(time, entry->modified_date()); +} + +TEST_F(FakeDriveServiceTest, SetLastModifiedTime_NonexistingFile) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + + const std::string kResourceId = "nonexisting_resource_id"; + base::Time time; + ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.SetLastModifiedTime( + kResourceId, + time, + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(HTTP_NOT_FOUND, error); + EXPECT_FALSE(entry); +} + +TEST_F(FakeDriveServiceTest, SetLastModifiedTime_Offline) { + ASSERT_TRUE(test_util::SetUpTestEntries(&fake_service_)); + fake_service_.set_offline(true); + + const std::string kResourceId = "2_file_resource_id"; + base::Time time; + ASSERT_TRUE(base::Time::FromString("1 April 2013 12:34:56", &time)); + + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + fake_service_.SetLastModifiedTime( + kResourceId, + time, + test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(DRIVE_NO_CONNECTION, error); + EXPECT_FALSE(entry); +} + +} // namespace + +} // namespace drive diff --git a/components/drive/service/test_util.cc b/components/drive/service/test_util.cc new file mode 100644 index 0000000..42c54f1 --- /dev/null +++ b/components/drive/service/test_util.cc @@ -0,0 +1,193 @@ +// Copyright 2014 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/service/test_util.h" + +#include "base/run_loop.h" +#include "components/drive/drive_api_util.h" +#include "components/drive/service/fake_drive_service.h" +#include "google_apis/drive/drive_api_parser.h" +#include "google_apis/drive/test_util.h" + +using google_apis::FileResource; +using google_apis::DRIVE_OTHER_ERROR; +using google_apis::DriveApiErrorCode; +using google_apis::HTTP_CREATED; + +namespace drive { +namespace test_util { + +bool SetUpTestEntries(FakeDriveService* drive_service) { + DriveApiErrorCode error = DRIVE_OTHER_ERROR; + scoped_ptr<FileResource> entry; + + drive_service->AddNewFileWithResourceId( + "2_file_resource_id", + "audio/mpeg", + "This is some test content.", + drive_service->GetRootResourceId(), + "File 1.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "slash_file_resource_id", + "audio/mpeg", + "This is some test content.", + drive_service->GetRootResourceId(), + "Slash / in file 1.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "3_file_resource_id", + "audio/mpeg", + "This is some test content.", + drive_service->GetRootResourceId(), + "Duplicate Name.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "4_file_resource_id", + "audio/mpeg", + "This is some test content.", + drive_service->GetRootResourceId(), + "Duplicate Name.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "5_document_resource_id", + util::kGoogleDocumentMimeType, + std::string(), + drive_service->GetRootResourceId(), + "Document 1 excludeDir-test", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "1_folder_resource_id", + util::kDriveFolderMimeType, + std::string(), + drive_service->GetRootResourceId(), + "Directory 1", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "subdirectory_file_1_id", + "audio/mpeg", + "This is some test content.", + "1_folder_resource_id", + "SubDirectory File 1.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "subdirectory_unowned_file_1_id", + "audio/mpeg", + "This is some test content.", + "1_folder_resource_id", + "Shared to The Account Owner.txt", + true, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewDirectoryWithResourceId( + "sub_dir_folder_resource_id", "1_folder_resource_id", + "Sub Directory Folder", AddNewDirectoryOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewDirectoryWithResourceId( + "sub_sub_directory_folder_id", "sub_dir_folder_resource_id", + "Sub Sub Directory Folder", AddNewDirectoryOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewDirectoryWithResourceId( + "slash_dir_folder_resource_id", drive_service->GetRootResourceId(), + "Slash / in directory", AddNewDirectoryOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "slash_subdir_file", + "audio/mpeg", + "This is some test content.", + "slash_dir_folder_resource_id", + "Slash SubDir File.txt", + false, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewDirectoryWithResourceId( + "sub_dir_folder_2_self_link", drive_service->GetRootResourceId(), + "Directory 2 excludeDir-test", AddNewDirectoryOptions(), + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "1_orphanfile_resource_id", + "text/plain", + "This is some test content.", + std::string(), + "Orphan File 1.txt", + true, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + drive_service->AddNewFileWithResourceId( + "orphan_doc_1", + util::kGoogleDocumentMimeType, + std::string(), + std::string(), + "Orphan Document", + true, // shared_with_me + google_apis::test_util::CreateCopyResultCallback(&error, &entry)); + base::RunLoop().RunUntilIdle(); + if (error != HTTP_CREATED) + return false; + + return true; +} + +} // namespace test_util +} // namespace drive diff --git a/components/drive/service/test_util.h b/components/drive/service/test_util.h new file mode 100644 index 0000000..9cd25ae --- /dev/null +++ b/components/drive/service/test_util.h @@ -0,0 +1,19 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_ +#define COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_ + +namespace drive { + +class FakeDriveService; + +namespace test_util { + +bool SetUpTestEntries(FakeDriveService* drive_service); + +} // namespace test_util +} // namespace drive + +#endif // COMPONENTS_DRIVE_SERVICE_TEST_UTIL_H_ |