1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <algorithm> 6 #include <map> 7 8 #include "base/bind.h" 9 #include "base/file_util.h" 10 #include "base/files/file_path.h" 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/json/json_reader.h" 13 #include "base/json/json_writer.h" 14 #include "base/message_loop/message_loop.h" 15 #include "base/run_loop.h" 16 #include "base/strings/string_number_conversions.h" 17 #include "base/strings/stringprintf.h" 18 #include "base/values.h" 19 #include "chrome/browser/google_apis/dummy_auth_service.h" 20 #include "chrome/browser/google_apis/gdata_wapi_parser.h" 21 #include "chrome/browser/google_apis/gdata_wapi_requests.h" 22 #include "chrome/browser/google_apis/gdata_wapi_url_generator.h" 23 #include "chrome/browser/google_apis/request_sender.h" 24 #include "chrome/browser/google_apis/test_util.h" 25 #include "net/base/escape.h" 26 #include "net/test/embedded_test_server/embedded_test_server.h" 27 #include "net/test/embedded_test_server/http_request.h" 28 #include "net/test/embedded_test_server/http_response.h" 29 #include "net/url_request/url_request_test_util.h" 30 #include "testing/gtest/include/gtest/gtest.h" 31 32 namespace google_apis { 33 34 namespace { 35 36 const char kTestUserAgent[] = "test-user-agent"; 37 const char kTestETag[] = "test_etag"; 38 const char kTestDownloadPathPrefix[] = "/download/"; 39 40 class GDataWapiRequestsTest : public testing::Test { 41 public: 42 GDataWapiRequestsTest() 43 : test_server_(message_loop_.message_loop_proxy()) { 44 } 45 46 virtual void SetUp() OVERRIDE { 47 request_context_getter_ = new net::TestURLRequestContextGetter( 48 message_loop_.message_loop_proxy()); 49 50 request_sender_.reset(new RequestSender(new DummyAuthService, 51 request_context_getter_.get(), 52 message_loop_.message_loop_proxy(), 53 kTestUserAgent)); 54 55 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); 56 57 ASSERT_TRUE(test_server_.InitializeAndWaitUntilReady()); 58 test_server_.RegisterRequestHandler( 59 base::Bind(&test_util::HandleDownloadFileRequest, 60 test_server_.base_url(), 61 base::Unretained(&http_request_))); 62 test_server_.RegisterRequestHandler( 63 base::Bind(&GDataWapiRequestsTest::HandleResourceFeedRequest, 64 base::Unretained(this))); 65 test_server_.RegisterRequestHandler( 66 base::Bind(&GDataWapiRequestsTest::HandleMetadataRequest, 67 base::Unretained(this))); 68 test_server_.RegisterRequestHandler( 69 base::Bind(&GDataWapiRequestsTest::HandleCreateSessionRequest, 70 base::Unretained(this))); 71 test_server_.RegisterRequestHandler( 72 base::Bind(&GDataWapiRequestsTest::HandleUploadRequest, 73 base::Unretained(this))); 74 test_server_.RegisterRequestHandler( 75 base::Bind(&GDataWapiRequestsTest::HandleDownloadRequest, 76 base::Unretained(this))); 77 78 GURL test_base_url = test_util::GetBaseUrlForTesting(test_server_.port()); 79 url_generator_.reset(new GDataWapiUrlGenerator( 80 test_base_url, test_base_url.Resolve(kTestDownloadPathPrefix))); 81 82 received_bytes_ = 0; 83 content_length_ = 0; 84 } 85 86 protected: 87 // Handles a request for fetching a resource feed. 88 scoped_ptr<net::test_server::HttpResponse> HandleResourceFeedRequest( 89 const net::test_server::HttpRequest& request) { 90 http_request_ = request; 91 92 const GURL absolute_url = test_server_.GetURL(request.relative_url); 93 std::string remaining_path; 94 if (absolute_url.path() == "/feeds/default/private/full" && 95 request.method == net::test_server::METHOD_POST) { 96 // This is a request for copying a document. 97 // TODO(satorux): we should generate valid JSON data for the newly 98 // copied document but for now, just return "file_entry.json" 99 scoped_ptr<net::test_server::BasicHttpResponse> result( 100 test_util::CreateHttpResponseFromFile( 101 test_util::GetTestFilePath("gdata/file_entry.json"))); 102 return result.PassAs<net::test_server::HttpResponse>(); 103 } 104 105 if (!test_util::RemovePrefix(absolute_url.path(), 106 "/feeds/default/private/full", 107 &remaining_path)) { 108 return scoped_ptr<net::test_server::HttpResponse>(); 109 } 110 111 if (remaining_path.empty()) { 112 // Process the default feed. 113 scoped_ptr<net::test_server::BasicHttpResponse> result( 114 test_util::CreateHttpResponseFromFile( 115 test_util::GetTestFilePath("gdata/root_feed.json"))); 116 return result.PassAs<net::test_server::HttpResponse>(); 117 } else { 118 // Process a feed for a single resource ID. 119 const std::string resource_id = net::UnescapeURLComponent( 120 remaining_path.substr(1), net::UnescapeRule::URL_SPECIAL_CHARS); 121 if (resource_id == "file:2_file_resource_id") { 122 scoped_ptr<net::test_server::BasicHttpResponse> result( 123 test_util::CreateHttpResponseFromFile( 124 test_util::GetTestFilePath("gdata/file_entry.json"))); 125 return result.PassAs<net::test_server::HttpResponse>(); 126 } else if (resource_id == "folder:root/contents" && 127 request.method == net::test_server::METHOD_POST) { 128 // This is a request for creating a directory in the root directory. 129 // TODO(satorux): we should generate valid JSON data for the newly 130 // created directory but for now, just return "directory_entry.json" 131 scoped_ptr<net::test_server::BasicHttpResponse> result( 132 test_util::CreateHttpResponseFromFile( 133 test_util::GetTestFilePath( 134 "gdata/directory_entry.json"))); 135 return result.PassAs<net::test_server::HttpResponse>(); 136 } else if (resource_id == 137 "folder:root/contents/file:2_file_resource_id" && 138 request.method == net::test_server::METHOD_DELETE) { 139 // This is a request for deleting a file from the root directory. 140 // TODO(satorux): Investigate what's returned from the server, and 141 // copy it. For now, just return a random file, as the contents don't 142 // matter. 143 scoped_ptr<net::test_server::BasicHttpResponse> result( 144 test_util::CreateHttpResponseFromFile( 145 test_util::GetTestFilePath("gdata/testfile.txt"))); 146 return result.PassAs<net::test_server::HttpResponse>(); 147 } else if (resource_id == "invalid_resource_id") { 148 // Check if this is an authorization request for an app. 149 // This emulates to return invalid formatted result from the server. 150 if (request.method == net::test_server::METHOD_PUT && 151 request.content.find("<docs:authorizedApp>") != std::string::npos) { 152 scoped_ptr<net::test_server::BasicHttpResponse> result( 153 test_util::CreateHttpResponseFromFile( 154 test_util::GetTestFilePath("gdata/testfile.txt"))); 155 return result.PassAs<net::test_server::HttpResponse>(); 156 } 157 } 158 } 159 160 return scoped_ptr<net::test_server::HttpResponse>(); 161 } 162 163 // Handles a request for fetching a metadata feed. 164 scoped_ptr<net::test_server::HttpResponse> HandleMetadataRequest( 165 const net::test_server::HttpRequest& request) { 166 http_request_ = request; 167 168 const GURL absolute_url = test_server_.GetURL(request.relative_url); 169 if (absolute_url.path() != "/feeds/metadata/default") 170 return scoped_ptr<net::test_server::HttpResponse>(); 171 172 scoped_ptr<net::test_server::BasicHttpResponse> result( 173 test_util::CreateHttpResponseFromFile( 174 test_util::GetTestFilePath( 175 "gdata/account_metadata.json"))); 176 if (absolute_url.query().find("include-installed-apps=true") == 177 string::npos) { 178 // Exclude the list of installed apps. 179 scoped_ptr<base::Value> parsed_content( 180 base::JSONReader::Read(result->content(), base::JSON_PARSE_RFC)); 181 CHECK(parsed_content); 182 183 // Remove the install apps node. 184 base::DictionaryValue* dictionary_value; 185 CHECK(parsed_content->GetAsDictionary(&dictionary_value)); 186 dictionary_value->Remove("entry.docs$installedApp", NULL); 187 188 // Write back it as the content of the result. 189 std::string content; 190 base::JSONWriter::Write(parsed_content.get(), &content); 191 result->set_content(content); 192 } 193 194 return result.PassAs<net::test_server::HttpResponse>(); 195 } 196 197 // Handles a request for creating a session for uploading. 198 scoped_ptr<net::test_server::HttpResponse> HandleCreateSessionRequest( 199 const net::test_server::HttpRequest& request) { 200 http_request_ = request; 201 202 const GURL absolute_url = test_server_.GetURL(request.relative_url); 203 if (StartsWithASCII(absolute_url.path(), 204 "/feeds/upload/create-session/default/private/full", 205 true)) { // case sensitive 206 // This is an initiating upload URL. 207 scoped_ptr<net::test_server::BasicHttpResponse> http_response( 208 new net::test_server::BasicHttpResponse); 209 210 // Check an ETag. 211 std::map<std::string, std::string>::const_iterator found = 212 request.headers.find("If-Match"); 213 if (found != request.headers.end() && 214 found->second != "*" && 215 found->second != kTestETag) { 216 http_response->set_code(net::HTTP_PRECONDITION_FAILED); 217 return http_response.PassAs<net::test_server::HttpResponse>(); 218 } 219 220 // Check if the X-Upload-Content-Length is present. If yes, store the 221 // length of the file. 222 found = request.headers.find("X-Upload-Content-Length"); 223 if (found == request.headers.end() || 224 !base::StringToInt64(found->second, &content_length_)) { 225 return scoped_ptr<net::test_server::HttpResponse>(); 226 } 227 received_bytes_ = 0; 228 229 http_response->set_code(net::HTTP_OK); 230 GURL upload_url; 231 // POST is used for a new file, and PUT is used for an existing file. 232 if (request.method == net::test_server::METHOD_POST) { 233 upload_url = test_server_.GetURL("/upload_new_file"); 234 } else if (request.method == net::test_server::METHOD_PUT) { 235 upload_url = test_server_.GetURL("/upload_existing_file"); 236 } else { 237 return scoped_ptr<net::test_server::HttpResponse>(); 238 } 239 http_response->AddCustomHeader("Location", upload_url.spec()); 240 return http_response.PassAs<net::test_server::HttpResponse>(); 241 } 242 243 return scoped_ptr<net::test_server::HttpResponse>(); 244 } 245 246 // Handles a request for uploading content. 247 scoped_ptr<net::test_server::HttpResponse> HandleUploadRequest( 248 const net::test_server::HttpRequest& request) { 249 http_request_ = request; 250 251 const GURL absolute_url = test_server_.GetURL(request.relative_url); 252 if (absolute_url.path() != "/upload_new_file" && 253 absolute_url.path() != "/upload_existing_file") { 254 return scoped_ptr<net::test_server::HttpResponse>(); 255 } 256 257 // TODO(satorux): We should create a correct JSON data for the uploaded 258 // file, but for now, just return file_entry.json. 259 scoped_ptr<net::test_server::BasicHttpResponse> response = 260 test_util::CreateHttpResponseFromFile( 261 test_util::GetTestFilePath("gdata/file_entry.json")); 262 // response.code() is set to SUCCESS. Change it to CREATED if it's a new 263 // file. 264 if (absolute_url.path() == "/upload_new_file") 265 response->set_code(net::HTTP_CREATED); 266 267 // Check if the Content-Range header is present. This must be present if 268 // the request body is not empty. 269 if (!request.content.empty()) { 270 std::map<std::string, std::string>::const_iterator iter = 271 request.headers.find("Content-Range"); 272 if (iter == request.headers.end()) 273 return scoped_ptr<net::test_server::HttpResponse>(); 274 int64 length = 0; 275 int64 start_position = 0; 276 int64 end_position = 0; 277 if (!test_util::ParseContentRangeHeader(iter->second, 278 &start_position, 279 &end_position, 280 &length)) { 281 return scoped_ptr<net::test_server::HttpResponse>(); 282 } 283 EXPECT_EQ(start_position, received_bytes_); 284 EXPECT_EQ(length, content_length_); 285 // end_position is inclusive, but so +1 to change the range to byte size. 286 received_bytes_ = end_position + 1; 287 } 288 289 // Add Range header to the response, based on the values of 290 // Content-Range header in the request. 291 // The header is annotated only when at least one byte is received. 292 if (received_bytes_ > 0) { 293 response->AddCustomHeader( 294 "Range", 295 "bytes=0-" + base::Int64ToString(received_bytes_ - 1)); 296 } 297 298 // Change the code to RESUME_INCOMPLETE if upload is not complete. 299 if (received_bytes_ < content_length_) 300 response->set_code(static_cast<net::HttpStatusCode>(308)); 301 302 return response.PassAs<net::test_server::HttpResponse>(); 303 } 304 305 // Handles a request for downloading a file. 306 scoped_ptr<net::test_server::HttpResponse> HandleDownloadRequest( 307 const net::test_server::HttpRequest& request) { 308 http_request_ = request; 309 310 const GURL absolute_url = test_server_.GetURL(request.relative_url); 311 std::string id; 312 if (!test_util::RemovePrefix(absolute_url.path(), 313 kTestDownloadPathPrefix, 314 &id)) { 315 return scoped_ptr<net::test_server::HttpResponse>(); 316 } 317 318 // For testing, returns a text with |id| repeated 3 times. 319 scoped_ptr<net::test_server::BasicHttpResponse> response( 320 new net::test_server::BasicHttpResponse); 321 response->set_code(net::HTTP_OK); 322 response->set_content(id + id + id); 323 response->set_content_type("text/plain"); 324 return response.PassAs<net::test_server::HttpResponse>(); 325 } 326 327 base::MessageLoopForIO message_loop_; // Test server needs IO thread. 328 net::test_server::EmbeddedTestServer test_server_; 329 scoped_ptr<RequestSender> request_sender_; 330 scoped_ptr<GDataWapiUrlGenerator> url_generator_; 331 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; 332 base::ScopedTempDir temp_dir_; 333 334 // These fields are used to keep the current upload state during a 335 // test case. These values are updated by the request from 336 // ResumeUploadRequest, and used to construct the response for 337 // both ResumeUploadRequest and GetUploadStatusRequest, to emulate 338 // the WAPI server. 339 int64 received_bytes_; 340 int64 content_length_; 341 342 // The incoming HTTP request is saved so tests can verify the request 343 // parameters like HTTP method (ex. some requests should use DELETE 344 // instead of GET). 345 net::test_server::HttpRequest http_request_; 346 }; 347 348 } // namespace 349 350 TEST_F(GDataWapiRequestsTest, GetResourceListRequest_DefaultFeed) { 351 GDataErrorCode result_code = GDATA_OTHER_ERROR; 352 scoped_ptr<ResourceList> result_data; 353 354 { 355 base::RunLoop run_loop; 356 GetResourceListRequest* request = new GetResourceListRequest( 357 request_sender_.get(), 358 *url_generator_, 359 GURL(), // Pass an empty URL to use the default feed 360 0, // start changestamp 361 std::string(), // search string 362 std::string(), // directory resource ID 363 test_util::CreateQuitCallback( 364 &run_loop, 365 test_util::CreateCopyResultCallback(&result_code, &result_data))); 366 request_sender_->StartRequestWithRetry(request); 367 run_loop.Run(); 368 } 369 370 EXPECT_EQ(HTTP_SUCCESS, result_code); 371 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 372 EXPECT_EQ("/feeds/default/private/full?v=3&alt=json&showroot=true&" 373 "showfolders=true&include-shared=true&max-results=500", 374 http_request_.relative_url); 375 376 // Sanity check of the result. 377 scoped_ptr<ResourceList> expected( 378 ResourceList::ExtractAndParse( 379 *test_util::LoadJSONFile("gdata/root_feed.json"))); 380 ASSERT_TRUE(result_data); 381 EXPECT_EQ(expected->title(), result_data->title()); 382 } 383 384 TEST_F(GDataWapiRequestsTest, GetResourceListRequest_ValidFeed) { 385 GDataErrorCode result_code = GDATA_OTHER_ERROR; 386 scoped_ptr<ResourceList> result_data; 387 388 { 389 base::RunLoop run_loop; 390 GetResourceListRequest* request = new GetResourceListRequest( 391 request_sender_.get(), 392 *url_generator_, 393 test_server_.GetURL("/files/gdata/root_feed.json"), 394 0, // start changestamp 395 std::string(), // search string 396 std::string(), // directory resource ID 397 test_util::CreateQuitCallback( 398 &run_loop, 399 test_util::CreateCopyResultCallback(&result_code, &result_data))); 400 request_sender_->StartRequestWithRetry(request); 401 run_loop.Run(); 402 } 403 404 EXPECT_EQ(HTTP_SUCCESS, result_code); 405 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 406 EXPECT_EQ("/files/gdata/root_feed.json?v=3&alt=json&showroot=true&" 407 "showfolders=true&include-shared=true&max-results=500", 408 http_request_.relative_url); 409 410 scoped_ptr<ResourceList> expected( 411 ResourceList::ExtractAndParse( 412 *test_util::LoadJSONFile("gdata/root_feed.json"))); 413 ASSERT_TRUE(result_data); 414 EXPECT_EQ(expected->title(), result_data->title()); 415 } 416 417 TEST_F(GDataWapiRequestsTest, GetResourceListRequest_InvalidFeed) { 418 // testfile.txt exists but the response is not JSON, so it should 419 // emit a parse error instead. 420 GDataErrorCode result_code = GDATA_OTHER_ERROR; 421 scoped_ptr<ResourceList> result_data; 422 423 { 424 base::RunLoop run_loop; 425 GetResourceListRequest* request = new GetResourceListRequest( 426 request_sender_.get(), 427 *url_generator_, 428 test_server_.GetURL("/files/gdata/testfile.txt"), 429 0, // start changestamp 430 std::string(), // search string 431 std::string(), // directory resource ID 432 test_util::CreateQuitCallback( 433 &run_loop, 434 test_util::CreateCopyResultCallback(&result_code, &result_data))); 435 request_sender_->StartRequestWithRetry(request); 436 run_loop.Run(); 437 } 438 439 EXPECT_EQ(GDATA_PARSE_ERROR, result_code); 440 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 441 EXPECT_EQ("/files/gdata/testfile.txt?v=3&alt=json&showroot=true&" 442 "showfolders=true&include-shared=true&max-results=500", 443 http_request_.relative_url); 444 EXPECT_FALSE(result_data); 445 } 446 447 TEST_F(GDataWapiRequestsTest, SearchByTitleRequest) { 448 GDataErrorCode result_code = GDATA_OTHER_ERROR; 449 scoped_ptr<ResourceList> result_data; 450 451 { 452 base::RunLoop run_loop; 453 SearchByTitleRequest* request = new SearchByTitleRequest( 454 request_sender_.get(), 455 *url_generator_, 456 "search-title", 457 std::string(), // directory resource id 458 test_util::CreateQuitCallback( 459 &run_loop, 460 test_util::CreateCopyResultCallback(&result_code, &result_data))); 461 request_sender_->StartRequestWithRetry(request); 462 run_loop.Run(); 463 } 464 465 EXPECT_EQ(HTTP_SUCCESS, result_code); 466 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 467 EXPECT_EQ("/feeds/default/private/full?v=3&alt=json&showroot=true&" 468 "showfolders=true&include-shared=true&max-results=500" 469 "&title=search-title&title-exact=true", 470 http_request_.relative_url); 471 EXPECT_TRUE(result_data); 472 } 473 474 TEST_F(GDataWapiRequestsTest, GetResourceEntryRequest_ValidResourceId) { 475 GDataErrorCode result_code = GDATA_OTHER_ERROR; 476 scoped_ptr<base::Value> result_data; 477 478 { 479 base::RunLoop run_loop; 480 GetResourceEntryRequest* request = new GetResourceEntryRequest( 481 request_sender_.get(), 482 *url_generator_, 483 "file:2_file_resource_id", // resource ID 484 GURL(), // embed origin 485 test_util::CreateQuitCallback( 486 &run_loop, 487 test_util::CreateCopyResultCallback(&result_code, &result_data))); 488 request_sender_->StartRequestWithRetry(request); 489 run_loop.Run(); 490 } 491 492 EXPECT_EQ(HTTP_SUCCESS, result_code); 493 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 494 EXPECT_EQ("/feeds/default/private/full/file%3A2_file_resource_id" 495 "?v=3&alt=json&showroot=true", 496 http_request_.relative_url); 497 EXPECT_TRUE(test_util::VerifyJsonData( 498 test_util::GetTestFilePath("gdata/file_entry.json"), 499 result_data.get())); 500 } 501 502 TEST_F(GDataWapiRequestsTest, GetResourceEntryRequest_InvalidResourceId) { 503 GDataErrorCode result_code = GDATA_OTHER_ERROR; 504 scoped_ptr<base::Value> result_data; 505 506 { 507 base::RunLoop run_loop; 508 GetResourceEntryRequest* request = new GetResourceEntryRequest( 509 request_sender_.get(), 510 *url_generator_, 511 "<invalid>", // resource ID 512 GURL(), // embed origin 513 test_util::CreateQuitCallback( 514 &run_loop, 515 test_util::CreateCopyResultCallback(&result_code, &result_data))); 516 request_sender_->StartRequestWithRetry(request); 517 run_loop.Run(); 518 } 519 520 EXPECT_EQ(HTTP_NOT_FOUND, result_code); 521 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 522 EXPECT_EQ("/feeds/default/private/full/%3Cinvalid%3E?v=3&alt=json" 523 "&showroot=true", 524 http_request_.relative_url); 525 ASSERT_FALSE(result_data); 526 } 527 528 TEST_F(GDataWapiRequestsTest, GetAccountMetadataRequest) { 529 GDataErrorCode result_code = GDATA_OTHER_ERROR; 530 scoped_ptr<AccountMetadata> result_data; 531 532 { 533 base::RunLoop run_loop; 534 GetAccountMetadataRequest* request = new GetAccountMetadataRequest( 535 request_sender_.get(), 536 *url_generator_, 537 test_util::CreateQuitCallback( 538 &run_loop, 539 test_util::CreateCopyResultCallback(&result_code, &result_data)), 540 true); // Include installed apps. 541 request_sender_->StartRequestWithRetry(request); 542 run_loop.Run(); 543 } 544 545 EXPECT_EQ(HTTP_SUCCESS, result_code); 546 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 547 EXPECT_EQ("/feeds/metadata/default?v=3&alt=json&showroot=true" 548 "&include-installed-apps=true", 549 http_request_.relative_url); 550 551 scoped_ptr<AccountMetadata> expected( 552 AccountMetadata::CreateFrom( 553 *test_util::LoadJSONFile("gdata/account_metadata.json"))); 554 555 ASSERT_TRUE(result_data.get()); 556 EXPECT_EQ(expected->largest_changestamp(), 557 result_data->largest_changestamp()); 558 EXPECT_EQ(expected->quota_bytes_total(), 559 result_data->quota_bytes_total()); 560 EXPECT_EQ(expected->quota_bytes_used(), 561 result_data->quota_bytes_used()); 562 563 // Sanity check for installed apps. 564 EXPECT_EQ(expected->installed_apps().size(), 565 result_data->installed_apps().size()); 566 } 567 568 TEST_F(GDataWapiRequestsTest, 569 GetAccountMetadataRequestWithoutInstalledApps) { 570 GDataErrorCode result_code = GDATA_OTHER_ERROR; 571 scoped_ptr<AccountMetadata> result_data; 572 573 { 574 base::RunLoop run_loop; 575 GetAccountMetadataRequest* request = new GetAccountMetadataRequest( 576 request_sender_.get(), 577 *url_generator_, 578 test_util::CreateQuitCallback( 579 &run_loop, 580 test_util::CreateCopyResultCallback(&result_code, &result_data)), 581 false); // Exclude installed apps. 582 request_sender_->StartRequestWithRetry(request); 583 run_loop.Run(); 584 } 585 586 EXPECT_EQ(HTTP_SUCCESS, result_code); 587 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 588 EXPECT_EQ("/feeds/metadata/default?v=3&alt=json&showroot=true", 589 http_request_.relative_url); 590 591 scoped_ptr<AccountMetadata> expected( 592 AccountMetadata::CreateFrom( 593 *test_util::LoadJSONFile("gdata/account_metadata.json"))); 594 595 ASSERT_TRUE(result_data.get()); 596 EXPECT_EQ(expected->largest_changestamp(), 597 result_data->largest_changestamp()); 598 EXPECT_EQ(expected->quota_bytes_total(), 599 result_data->quota_bytes_total()); 600 EXPECT_EQ(expected->quota_bytes_used(), 601 result_data->quota_bytes_used()); 602 603 // Installed apps shouldn't be included. 604 EXPECT_EQ(0U, result_data->installed_apps().size()); 605 } 606 607 TEST_F(GDataWapiRequestsTest, DeleteResourceRequest) { 608 GDataErrorCode result_code = GDATA_OTHER_ERROR; 609 610 { 611 base::RunLoop run_loop; 612 DeleteResourceRequest* request = new DeleteResourceRequest( 613 request_sender_.get(), 614 *url_generator_, 615 test_util::CreateQuitCallback( 616 &run_loop, 617 test_util::CreateCopyResultCallback(&result_code)), 618 "file:2_file_resource_id", 619 std::string()); 620 621 request_sender_->StartRequestWithRetry(request); 622 run_loop.Run(); 623 } 624 625 EXPECT_EQ(HTTP_SUCCESS, result_code); 626 EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method); 627 EXPECT_EQ( 628 "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json" 629 "&showroot=true", 630 http_request_.relative_url); 631 EXPECT_EQ("*", http_request_.headers["If-Match"]); 632 } 633 634 TEST_F(GDataWapiRequestsTest, DeleteResourceRequestWithETag) { 635 GDataErrorCode result_code = GDATA_OTHER_ERROR; 636 637 { 638 base::RunLoop run_loop; 639 DeleteResourceRequest* request = new DeleteResourceRequest( 640 request_sender_.get(), 641 *url_generator_, 642 test_util::CreateQuitCallback( 643 &run_loop, 644 test_util::CreateCopyResultCallback(&result_code)), 645 "file:2_file_resource_id", 646 "etag"); 647 648 request_sender_->StartRequestWithRetry(request); 649 run_loop.Run(); 650 } 651 652 EXPECT_EQ(HTTP_SUCCESS, result_code); 653 EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method); 654 EXPECT_EQ( 655 "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json" 656 "&showroot=true", 657 http_request_.relative_url); 658 EXPECT_EQ("etag", http_request_.headers["If-Match"]); 659 } 660 661 TEST_F(GDataWapiRequestsTest, CreateDirectoryRequest) { 662 GDataErrorCode result_code = GDATA_OTHER_ERROR; 663 scoped_ptr<base::Value> result_data; 664 665 // Create "new directory" in the root directory. 666 { 667 base::RunLoop run_loop; 668 CreateDirectoryRequest* request = new CreateDirectoryRequest( 669 request_sender_.get(), 670 *url_generator_, 671 test_util::CreateQuitCallback( 672 &run_loop, 673 test_util::CreateCopyResultCallback(&result_code, &result_data)), 674 "folder:root", 675 "new directory"); 676 677 request_sender_->StartRequestWithRetry(request); 678 run_loop.Run(); 679 } 680 681 EXPECT_EQ(HTTP_SUCCESS, result_code); 682 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 683 EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents?v=3&alt=json" 684 "&showroot=true", 685 http_request_.relative_url); 686 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 687 688 EXPECT_TRUE(http_request_.has_content); 689 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 690 "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" 691 " <category scheme=\"http://schemas.google.com/g/2005#kind\" " 692 "term=\"http://schemas.google.com/docs/2007#folder\"/>\n" 693 " <title>new directory</title>\n" 694 "</entry>\n", 695 http_request_.content); 696 } 697 698 TEST_F(GDataWapiRequestsTest, CopyHostedDocumentRequest) { 699 GDataErrorCode result_code = GDATA_OTHER_ERROR; 700 scoped_ptr<base::Value> result_data; 701 702 // Copy a document with a new name "New Document". 703 { 704 base::RunLoop run_loop; 705 CopyHostedDocumentRequest* request = new CopyHostedDocumentRequest( 706 request_sender_.get(), 707 *url_generator_, 708 test_util::CreateQuitCallback( 709 &run_loop, 710 test_util::CreateCopyResultCallback(&result_code, &result_data)), 711 "document:5_document_resource_id", // source resource ID 712 "New Document"); 713 714 request_sender_->StartRequestWithRetry(request); 715 run_loop.Run(); 716 } 717 718 EXPECT_EQ(HTTP_SUCCESS, result_code); 719 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 720 EXPECT_EQ("/feeds/default/private/full?v=3&alt=json&showroot=true", 721 http_request_.relative_url); 722 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 723 724 EXPECT_TRUE(http_request_.has_content); 725 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 726 "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" 727 " <id>document:5_document_resource_id</id>\n" 728 " <title>New Document</title>\n" 729 "</entry>\n", 730 http_request_.content); 731 } 732 733 TEST_F(GDataWapiRequestsTest, RenameResourceRequest) { 734 GDataErrorCode result_code = GDATA_OTHER_ERROR; 735 736 // Rename a file with a new name "New File". 737 { 738 base::RunLoop run_loop; 739 RenameResourceRequest* request = new RenameResourceRequest( 740 request_sender_.get(), 741 *url_generator_, 742 test_util::CreateQuitCallback( 743 &run_loop, 744 test_util::CreateCopyResultCallback(&result_code)), 745 "file:2_file_resource_id", 746 "New File"); 747 748 request_sender_->StartRequestWithRetry(request); 749 run_loop.Run(); 750 } 751 752 EXPECT_EQ(HTTP_SUCCESS, result_code); 753 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 754 EXPECT_EQ( 755 "/feeds/default/private/full/file%3A2_file_resource_id?v=3&alt=json" 756 "&showroot=true", 757 http_request_.relative_url); 758 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 759 EXPECT_EQ("*", http_request_.headers["If-Match"]); 760 761 EXPECT_TRUE(http_request_.has_content); 762 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 763 "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" 764 " <title>New File</title>\n" 765 "</entry>\n", 766 http_request_.content); 767 } 768 769 TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_ValidFeed) { 770 GDataErrorCode result_code = GDATA_OTHER_ERROR; 771 GURL result_data; 772 773 // Authorize an app with APP_ID to access to a document. 774 { 775 base::RunLoop run_loop; 776 AuthorizeAppRequest* request = new AuthorizeAppRequest( 777 request_sender_.get(), 778 *url_generator_, 779 test_util::CreateQuitCallback( 780 &run_loop, 781 test_util::CreateCopyResultCallback(&result_code, &result_data)), 782 "file:2_file_resource_id", 783 "the_app_id"); 784 785 request_sender_->StartRequestWithRetry(request); 786 run_loop.Run(); 787 } 788 789 EXPECT_EQ(HTTP_SUCCESS, result_code); 790 EXPECT_EQ(GURL("https://entry1_open_with_link/"), result_data); 791 792 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 793 EXPECT_EQ("/feeds/default/private/full/file%3A2_file_resource_id" 794 "?v=3&alt=json&showroot=true", 795 http_request_.relative_url); 796 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 797 EXPECT_EQ("*", http_request_.headers["If-Match"]); 798 799 EXPECT_TRUE(http_request_.has_content); 800 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 801 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 802 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 803 " <docs:authorizedApp>the_app_id</docs:authorizedApp>\n" 804 "</entry>\n", 805 http_request_.content); 806 } 807 808 TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_NotFound) { 809 GDataErrorCode result_code = GDATA_OTHER_ERROR; 810 GURL result_data; 811 812 // Authorize an app with APP_ID to access to a document. 813 { 814 base::RunLoop run_loop; 815 AuthorizeAppRequest* request = new AuthorizeAppRequest( 816 request_sender_.get(), 817 *url_generator_, 818 test_util::CreateQuitCallback( 819 &run_loop, 820 test_util::CreateCopyResultCallback(&result_code, &result_data)), 821 "file:2_file_resource_id", 822 "unauthorized_app_id"); 823 824 request_sender_->StartRequestWithRetry(request); 825 run_loop.Run(); 826 } 827 828 EXPECT_EQ(GDATA_OTHER_ERROR, result_code); 829 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 830 EXPECT_EQ("/feeds/default/private/full/file%3A2_file_resource_id" 831 "?v=3&alt=json&showroot=true", 832 http_request_.relative_url); 833 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 834 EXPECT_EQ("*", http_request_.headers["If-Match"]); 835 836 EXPECT_TRUE(http_request_.has_content); 837 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 838 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 839 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 840 " <docs:authorizedApp>unauthorized_app_id</docs:authorizedApp>\n" 841 "</entry>\n", 842 http_request_.content); 843 } 844 845 TEST_F(GDataWapiRequestsTest, AuthorizeAppRequest_InvalidFeed) { 846 GDataErrorCode result_code = GDATA_OTHER_ERROR; 847 GURL result_data; 848 849 // Authorize an app with APP_ID to access to a document but an invalid feed. 850 { 851 base::RunLoop run_loop; 852 AuthorizeAppRequest* request = new AuthorizeAppRequest( 853 request_sender_.get(), 854 *url_generator_, 855 test_util::CreateQuitCallback( 856 &run_loop, 857 test_util::CreateCopyResultCallback(&result_code, &result_data)), 858 "invalid_resource_id", 859 "APP_ID"); 860 861 request_sender_->StartRequestWithRetry(request); 862 run_loop.Run(); 863 } 864 865 EXPECT_EQ(GDATA_PARSE_ERROR, result_code); 866 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 867 EXPECT_EQ("/feeds/default/private/full/invalid_resource_id" 868 "?v=3&alt=json&showroot=true", 869 http_request_.relative_url); 870 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 871 EXPECT_EQ("*", http_request_.headers["If-Match"]); 872 873 EXPECT_TRUE(http_request_.has_content); 874 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 875 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 876 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 877 " <docs:authorizedApp>APP_ID</docs:authorizedApp>\n" 878 "</entry>\n", 879 http_request_.content); 880 } 881 882 TEST_F(GDataWapiRequestsTest, AddResourceToDirectoryRequest) { 883 GDataErrorCode result_code = GDATA_OTHER_ERROR; 884 885 // Add a file to the root directory. 886 { 887 base::RunLoop run_loop; 888 AddResourceToDirectoryRequest* request = 889 new AddResourceToDirectoryRequest( 890 request_sender_.get(), 891 *url_generator_, 892 test_util::CreateQuitCallback( 893 &run_loop, 894 test_util::CreateCopyResultCallback(&result_code)), 895 "folder:root", 896 "file:2_file_resource_id"); 897 898 request_sender_->StartRequestWithRetry(request); 899 run_loop.Run(); 900 } 901 902 EXPECT_EQ(HTTP_SUCCESS, result_code); 903 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 904 EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents?v=3&alt=json" 905 "&showroot=true", 906 http_request_.relative_url); 907 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 908 909 EXPECT_TRUE(http_request_.has_content); 910 EXPECT_EQ(base::StringPrintf("<?xml version=\"1.0\"?>\n" 911 "<entry xmlns=\"http://www.w3.org/2005/Atom\">\n" 912 " <id>%sfeeds/default/private/full/" 913 "file%%3A2_file_resource_id</id>\n" 914 "</entry>\n", 915 test_server_.base_url().spec().c_str()), 916 http_request_.content); 917 } 918 919 TEST_F(GDataWapiRequestsTest, RemoveResourceFromDirectoryRequest) { 920 GDataErrorCode result_code = GDATA_OTHER_ERROR; 921 922 // Remove a file from the root directory. 923 { 924 base::RunLoop run_loop; 925 RemoveResourceFromDirectoryRequest* request = 926 new RemoveResourceFromDirectoryRequest( 927 request_sender_.get(), 928 *url_generator_, 929 test_util::CreateQuitCallback( 930 &run_loop, 931 test_util::CreateCopyResultCallback(&result_code)), 932 "folder:root", 933 "file:2_file_resource_id"); 934 935 request_sender_->StartRequestWithRetry(request); 936 run_loop.Run(); 937 } 938 939 EXPECT_EQ(HTTP_SUCCESS, result_code); 940 // DELETE method should be used, without the body content. 941 EXPECT_EQ(net::test_server::METHOD_DELETE, http_request_.method); 942 EXPECT_EQ("/feeds/default/private/full/folder%3Aroot/contents/" 943 "file%3A2_file_resource_id?v=3&alt=json&showroot=true", 944 http_request_.relative_url); 945 EXPECT_EQ("*", http_request_.headers["If-Match"]); 946 EXPECT_FALSE(http_request_.has_content); 947 } 948 949 // This test exercises InitiateUploadNewFileRequest and 950 // ResumeUploadRequest for a scenario of uploading a new file. 951 TEST_F(GDataWapiRequestsTest, UploadNewFile) { 952 const std::string kUploadContent = "hello"; 953 const base::FilePath kTestFilePath = 954 temp_dir_.path().AppendASCII("upload_file.txt"); 955 ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent)); 956 957 GDataErrorCode result_code = GDATA_OTHER_ERROR; 958 GURL upload_url; 959 960 // 1) Get the upload URL for uploading a new file. 961 { 962 base::RunLoop run_loop; 963 InitiateUploadNewFileRequest* initiate_request = 964 new InitiateUploadNewFileRequest( 965 request_sender_.get(), 966 *url_generator_, 967 test_util::CreateQuitCallback( 968 &run_loop, 969 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 970 "text/plain", 971 kUploadContent.size(), 972 "folder:id", 973 "New file"); 974 request_sender_->StartRequestWithRetry(initiate_request); 975 run_loop.Run(); 976 } 977 978 EXPECT_EQ(HTTP_SUCCESS, result_code); 979 EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url); 980 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 981 // convert=false should be passed as files should be uploaded as-is. 982 EXPECT_EQ( 983 "/feeds/upload/create-session/default/private/full/folder%3Aid/contents" 984 "?convert=false&v=3&alt=json&showroot=true", 985 http_request_.relative_url); 986 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 987 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 988 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 989 http_request_.headers["X-Upload-Content-Length"]); 990 991 EXPECT_TRUE(http_request_.has_content); 992 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 993 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 994 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 995 " <title>New file</title>\n" 996 "</entry>\n", 997 http_request_.content); 998 999 // 2) Upload the content to the upload URL. 1000 UploadRangeResponse response; 1001 scoped_ptr<ResourceEntry> new_entry; 1002 1003 { 1004 base::RunLoop run_loop; 1005 ResumeUploadRequest* resume_request = new ResumeUploadRequest( 1006 request_sender_.get(), 1007 test_util::CreateQuitCallback( 1008 &run_loop, 1009 test_util::CreateCopyResultCallback(&response, &new_entry)), 1010 ProgressCallback(), 1011 upload_url, 1012 0, // start_position 1013 kUploadContent.size(), // end_position (exclusive) 1014 kUploadContent.size(), // content_length, 1015 "text/plain", // content_type 1016 kTestFilePath); 1017 1018 request_sender_->StartRequestWithRetry(resume_request); 1019 run_loop.Run(); 1020 } 1021 1022 // METHOD_PUT should be used to upload data. 1023 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1024 // Request should go to the upload URL. 1025 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1026 // Content-Range header should be added. 1027 EXPECT_EQ("bytes 0-" + 1028 base::Int64ToString(kUploadContent.size() -1) + "/" + 1029 base::Int64ToString(kUploadContent.size()), 1030 http_request_.headers["Content-Range"]); 1031 // The upload content should be set in the HTTP request. 1032 EXPECT_TRUE(http_request_.has_content); 1033 EXPECT_EQ(kUploadContent, http_request_.content); 1034 1035 // Check the response. 1036 EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file 1037 // The start and end positions should be set to -1, if an upload is complete. 1038 EXPECT_EQ(-1, response.start_position_received); 1039 EXPECT_EQ(-1, response.end_position_received); 1040 } 1041 1042 // This test exercises InitiateUploadNewFileRequest and ResumeUploadRequest 1043 // for a scenario of uploading a new *large* file, which requires multiple 1044 // requests of ResumeUploadRequest. GetUploadRequest is also tested in this 1045 // test case. 1046 TEST_F(GDataWapiRequestsTest, UploadNewLargeFile) { 1047 const size_t kMaxNumBytes = 10; 1048 // This is big enough to cause multiple requests of ResumeUploadRequest 1049 // as we are going to send at most kMaxNumBytes at a time. 1050 // So, sending "kMaxNumBytes * 2 + 1" bytes ensures three 1051 // ResumeUploadRequests, which are start, middle and last requests. 1052 const std::string kUploadContent(kMaxNumBytes * 2 + 1, 'a'); 1053 const base::FilePath kTestFilePath = 1054 temp_dir_.path().AppendASCII("upload_file.txt"); 1055 ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent)); 1056 1057 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1058 GURL upload_url; 1059 1060 // 1) Get the upload URL for uploading a new file. 1061 { 1062 base::RunLoop run_loop; 1063 InitiateUploadNewFileRequest* initiate_request = 1064 new InitiateUploadNewFileRequest( 1065 request_sender_.get(), 1066 *url_generator_, 1067 test_util::CreateQuitCallback( 1068 &run_loop, 1069 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 1070 "text/plain", 1071 kUploadContent.size(), 1072 "folder:id", 1073 "New file"); 1074 request_sender_->StartRequestWithRetry(initiate_request); 1075 run_loop.Run(); 1076 } 1077 1078 EXPECT_EQ(HTTP_SUCCESS, result_code); 1079 EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url); 1080 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 1081 // convert=false should be passed as files should be uploaded as-is. 1082 EXPECT_EQ( 1083 "/feeds/upload/create-session/default/private/full/folder%3Aid/contents" 1084 "?convert=false&v=3&alt=json&showroot=true", 1085 http_request_.relative_url); 1086 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 1087 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 1088 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 1089 http_request_.headers["X-Upload-Content-Length"]); 1090 1091 EXPECT_TRUE(http_request_.has_content); 1092 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 1093 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 1094 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 1095 " <title>New file</title>\n" 1096 "</entry>\n", 1097 http_request_.content); 1098 1099 // 2) Before sending any data, check the current status. 1100 // This is an edge case test for GetUploadStatusRequest 1101 // (UploadRangeRequestBase). 1102 { 1103 UploadRangeResponse response; 1104 scoped_ptr<ResourceEntry> new_entry; 1105 1106 // Check the response by GetUploadStatusRequest. 1107 { 1108 base::RunLoop run_loop; 1109 GetUploadStatusRequest* get_upload_status_request = 1110 new GetUploadStatusRequest( 1111 request_sender_.get(), 1112 test_util::CreateQuitCallback( 1113 &run_loop, 1114 test_util::CreateCopyResultCallback(&response, &new_entry)), 1115 upload_url, 1116 kUploadContent.size()); 1117 request_sender_->StartRequestWithRetry(get_upload_status_request); 1118 run_loop.Run(); 1119 } 1120 1121 // METHOD_PUT should be used to upload data. 1122 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1123 // Request should go to the upload URL. 1124 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1125 // Content-Range header should be added. 1126 EXPECT_EQ("bytes */" + base::Int64ToString(kUploadContent.size()), 1127 http_request_.headers["Content-Range"]); 1128 EXPECT_TRUE(http_request_.has_content); 1129 EXPECT_TRUE(http_request_.content.empty()); 1130 1131 // Check the response. 1132 EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code); 1133 EXPECT_EQ(0, response.start_position_received); 1134 EXPECT_EQ(0, response.end_position_received); 1135 } 1136 1137 // 3) Upload the content to the upload URL with multiple requests. 1138 size_t num_bytes_consumed = 0; 1139 for (size_t start_position = 0; start_position < kUploadContent.size(); 1140 start_position += kMaxNumBytes) { 1141 SCOPED_TRACE(testing::Message("start_position: ") << start_position); 1142 1143 // The payload is at most kMaxNumBytes. 1144 const size_t remaining_size = kUploadContent.size() - start_position; 1145 const std::string payload = kUploadContent.substr( 1146 start_position, std::min(kMaxNumBytes, remaining_size)); 1147 num_bytes_consumed += payload.size(); 1148 // The end position is exclusive. 1149 const size_t end_position = start_position + payload.size(); 1150 1151 UploadRangeResponse response; 1152 scoped_ptr<ResourceEntry> new_entry; 1153 1154 { 1155 base::RunLoop run_loop; 1156 ResumeUploadRequest* resume_request = new ResumeUploadRequest( 1157 request_sender_.get(), 1158 test_util::CreateQuitCallback( 1159 &run_loop, 1160 test_util::CreateCopyResultCallback(&response, &new_entry)), 1161 ProgressCallback(), 1162 upload_url, 1163 start_position, 1164 end_position, 1165 kUploadContent.size(), // content_length, 1166 "text/plain", // content_type 1167 kTestFilePath); 1168 request_sender_->StartRequestWithRetry(resume_request); 1169 run_loop.Run(); 1170 } 1171 1172 // METHOD_PUT should be used to upload data. 1173 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1174 // Request should go to the upload URL. 1175 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1176 // Content-Range header should be added. 1177 EXPECT_EQ("bytes " + 1178 base::Int64ToString(start_position) + "-" + 1179 base::Int64ToString(end_position - 1) + "/" + 1180 base::Int64ToString(kUploadContent.size()), 1181 http_request_.headers["Content-Range"]); 1182 // The upload content should be set in the HTTP request. 1183 EXPECT_TRUE(http_request_.has_content); 1184 EXPECT_EQ(payload, http_request_.content); 1185 1186 // Check the response. 1187 if (payload.size() == remaining_size) { 1188 EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file. 1189 // The start and end positions should be set to -1, if an upload is 1190 // complete. 1191 EXPECT_EQ(-1, response.start_position_received); 1192 EXPECT_EQ(-1, response.end_position_received); 1193 // The upload process is completed, so exit from the loop. 1194 break; 1195 } 1196 1197 EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code); 1198 EXPECT_EQ(0, response.start_position_received); 1199 EXPECT_EQ(static_cast<int64>(end_position), 1200 response.end_position_received); 1201 1202 // Check the response by GetUploadStatusRequest. 1203 { 1204 base::RunLoop run_loop; 1205 GetUploadStatusRequest* get_upload_status_request = 1206 new GetUploadStatusRequest( 1207 request_sender_.get(), 1208 test_util::CreateQuitCallback( 1209 &run_loop, 1210 test_util::CreateCopyResultCallback(&response, &new_entry)), 1211 upload_url, 1212 kUploadContent.size()); 1213 request_sender_->StartRequestWithRetry(get_upload_status_request); 1214 run_loop.Run(); 1215 } 1216 1217 // METHOD_PUT should be used to upload data. 1218 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1219 // Request should go to the upload URL. 1220 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1221 // Content-Range header should be added. 1222 EXPECT_EQ("bytes */" + base::Int64ToString(kUploadContent.size()), 1223 http_request_.headers["Content-Range"]); 1224 EXPECT_TRUE(http_request_.has_content); 1225 EXPECT_TRUE(http_request_.content.empty()); 1226 1227 // Check the response. 1228 EXPECT_EQ(HTTP_RESUME_INCOMPLETE, response.code); 1229 EXPECT_EQ(0, response.start_position_received); 1230 EXPECT_EQ(static_cast<int64>(end_position), 1231 response.end_position_received); 1232 } 1233 1234 EXPECT_EQ(kUploadContent.size(), num_bytes_consumed); 1235 } 1236 1237 // This test exercises InitiateUploadNewFileRequest and ResumeUploadRequest 1238 // for a scenario of uploading a new *empty* file. 1239 // 1240 // The test is almost identical to UploadNewFile. The only difference is the 1241 // expectation for the Content-Range header. 1242 TEST_F(GDataWapiRequestsTest, UploadNewEmptyFile) { 1243 const std::string kUploadContent; 1244 const base::FilePath kTestFilePath = 1245 temp_dir_.path().AppendASCII("empty_file.txt"); 1246 ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent)); 1247 1248 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1249 GURL upload_url; 1250 1251 // 1) Get the upload URL for uploading a new file. 1252 { 1253 base::RunLoop run_loop; 1254 InitiateUploadNewFileRequest* initiate_request = 1255 new InitiateUploadNewFileRequest( 1256 request_sender_.get(), 1257 *url_generator_, 1258 test_util::CreateQuitCallback( 1259 &run_loop, 1260 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 1261 "text/plain", 1262 kUploadContent.size(), 1263 "folder:id", 1264 "New file"); 1265 request_sender_->StartRequestWithRetry(initiate_request); 1266 run_loop.Run(); 1267 } 1268 1269 EXPECT_EQ(HTTP_SUCCESS, result_code); 1270 EXPECT_EQ(test_server_.GetURL("/upload_new_file"), upload_url); 1271 EXPECT_EQ(net::test_server::METHOD_POST, http_request_.method); 1272 // convert=false should be passed as files should be uploaded as-is. 1273 EXPECT_EQ( 1274 "/feeds/upload/create-session/default/private/full/folder%3Aid/contents" 1275 "?convert=false&v=3&alt=json&showroot=true", 1276 http_request_.relative_url); 1277 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 1278 EXPECT_EQ("application/atom+xml", http_request_.headers["Content-Type"]); 1279 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 1280 http_request_.headers["X-Upload-Content-Length"]); 1281 1282 EXPECT_TRUE(http_request_.has_content); 1283 EXPECT_EQ("<?xml version=\"1.0\"?>\n" 1284 "<entry xmlns=\"http://www.w3.org/2005/Atom\" " 1285 "xmlns:docs=\"http://schemas.google.com/docs/2007\">\n" 1286 " <title>New file</title>\n" 1287 "</entry>\n", 1288 http_request_.content); 1289 1290 // 2) Upload the content to the upload URL. 1291 UploadRangeResponse response; 1292 scoped_ptr<ResourceEntry> new_entry; 1293 1294 { 1295 base::RunLoop run_loop; 1296 ResumeUploadRequest* resume_request = new ResumeUploadRequest( 1297 request_sender_.get(), 1298 test_util::CreateQuitCallback( 1299 &run_loop, 1300 test_util::CreateCopyResultCallback(&response, &new_entry)), 1301 ProgressCallback(), 1302 upload_url, 1303 0, // start_position 1304 kUploadContent.size(), // end_position (exclusive) 1305 kUploadContent.size(), // content_length, 1306 "text/plain", // content_type 1307 kTestFilePath); 1308 request_sender_->StartRequestWithRetry(resume_request); 1309 run_loop.Run(); 1310 } 1311 1312 // METHOD_PUT should be used to upload data. 1313 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1314 // Request should go to the upload URL. 1315 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1316 // Content-Range header should not exit if the content is empty. 1317 // We should not generate the header with an invalid value "bytes 0--1/0". 1318 EXPECT_EQ(0U, http_request_.headers.count("Content-Range")); 1319 // The upload content should be set in the HTTP request. 1320 EXPECT_TRUE(http_request_.has_content); 1321 EXPECT_EQ(kUploadContent, http_request_.content); 1322 1323 // Check the response. 1324 EXPECT_EQ(HTTP_CREATED, response.code); // Because it's a new file. 1325 // The start and end positions should be set to -1, if an upload is complete. 1326 EXPECT_EQ(-1, response.start_position_received); 1327 EXPECT_EQ(-1, response.end_position_received); 1328 } 1329 1330 // This test exercises InitiateUploadExistingFileRequest and 1331 // ResumeUploadRequest for a scenario of updating an existing file. 1332 TEST_F(GDataWapiRequestsTest, UploadExistingFile) { 1333 const std::string kUploadContent = "hello"; 1334 const base::FilePath kTestFilePath = 1335 temp_dir_.path().AppendASCII("upload_file.txt"); 1336 ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent)); 1337 1338 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1339 GURL upload_url; 1340 1341 // 1) Get the upload URL for uploading an existing file. 1342 { 1343 base::RunLoop run_loop; 1344 InitiateUploadExistingFileRequest* initiate_request = 1345 new InitiateUploadExistingFileRequest( 1346 request_sender_.get(), 1347 *url_generator_, 1348 test_util::CreateQuitCallback( 1349 &run_loop, 1350 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 1351 "text/plain", 1352 kUploadContent.size(), 1353 "file:foo", 1354 std::string() /* etag */); 1355 request_sender_->StartRequestWithRetry(initiate_request); 1356 run_loop.Run(); 1357 } 1358 1359 EXPECT_EQ(HTTP_SUCCESS, result_code); 1360 EXPECT_EQ(test_server_.GetURL("/upload_existing_file"), upload_url); 1361 // For updating an existing file, METHOD_PUT should be used. 1362 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1363 // convert=false should be passed as files should be uploaded as-is. 1364 EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo" 1365 "?convert=false&v=3&alt=json&showroot=true", 1366 http_request_.relative_url); 1367 // Even though the body is empty, the content type should be set to 1368 // "text/plain". 1369 EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]); 1370 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 1371 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 1372 http_request_.headers["X-Upload-Content-Length"]); 1373 // For updating an existing file, an empty body should be attached (PUT 1374 // requires a body) 1375 EXPECT_TRUE(http_request_.has_content); 1376 EXPECT_EQ("", http_request_.content); 1377 EXPECT_EQ("*", http_request_.headers["If-Match"]); 1378 1379 // 2) Upload the content to the upload URL. 1380 UploadRangeResponse response; 1381 scoped_ptr<ResourceEntry> new_entry; 1382 1383 { 1384 base::RunLoop run_loop; 1385 ResumeUploadRequest* resume_request = new ResumeUploadRequest( 1386 request_sender_.get(), 1387 test_util::CreateQuitCallback( 1388 &run_loop, 1389 test_util::CreateCopyResultCallback(&response, &new_entry)), 1390 ProgressCallback(), 1391 upload_url, 1392 0, // start_position 1393 kUploadContent.size(), // end_position (exclusive) 1394 kUploadContent.size(), // content_length, 1395 "text/plain", // content_type 1396 kTestFilePath); 1397 1398 request_sender_->StartRequestWithRetry(resume_request); 1399 run_loop.Run(); 1400 } 1401 1402 // METHOD_PUT should be used to upload data. 1403 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1404 // Request should go to the upload URL. 1405 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1406 // Content-Range header should be added. 1407 EXPECT_EQ("bytes 0-" + 1408 base::Int64ToString(kUploadContent.size() -1) + "/" + 1409 base::Int64ToString(kUploadContent.size()), 1410 http_request_.headers["Content-Range"]); 1411 // The upload content should be set in the HTTP request. 1412 EXPECT_TRUE(http_request_.has_content); 1413 EXPECT_EQ(kUploadContent, http_request_.content); 1414 1415 // Check the response. 1416 EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file. 1417 // The start and end positions should be set to -1, if an upload is complete. 1418 EXPECT_EQ(-1, response.start_position_received); 1419 EXPECT_EQ(-1, response.end_position_received); 1420 } 1421 1422 // This test exercises InitiateUploadExistingFileRequest and 1423 // ResumeUploadRequest for a scenario of updating an existing file. 1424 TEST_F(GDataWapiRequestsTest, UploadExistingFileWithETag) { 1425 const std::string kUploadContent = "hello"; 1426 const base::FilePath kTestFilePath = 1427 temp_dir_.path().AppendASCII("upload_file.txt"); 1428 ASSERT_TRUE(test_util::WriteStringToFile(kTestFilePath, kUploadContent)); 1429 1430 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1431 GURL upload_url; 1432 1433 // 1) Get the upload URL for uploading an existing file. 1434 { 1435 base::RunLoop run_loop; 1436 InitiateUploadExistingFileRequest* initiate_request = 1437 new InitiateUploadExistingFileRequest( 1438 request_sender_.get(), 1439 *url_generator_, 1440 test_util::CreateQuitCallback( 1441 &run_loop, 1442 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 1443 "text/plain", 1444 kUploadContent.size(), 1445 "file:foo", 1446 kTestETag); 1447 request_sender_->StartRequestWithRetry(initiate_request); 1448 run_loop.Run(); 1449 } 1450 1451 EXPECT_EQ(HTTP_SUCCESS, result_code); 1452 EXPECT_EQ(test_server_.GetURL("/upload_existing_file"), upload_url); 1453 // For updating an existing file, METHOD_PUT should be used. 1454 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1455 // convert=false should be passed as files should be uploaded as-is. 1456 EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo" 1457 "?convert=false&v=3&alt=json&showroot=true", 1458 http_request_.relative_url); 1459 // Even though the body is empty, the content type should be set to 1460 // "text/plain". 1461 EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]); 1462 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 1463 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 1464 http_request_.headers["X-Upload-Content-Length"]); 1465 // For updating an existing file, an empty body should be attached (PUT 1466 // requires a body) 1467 EXPECT_TRUE(http_request_.has_content); 1468 EXPECT_EQ("", http_request_.content); 1469 EXPECT_EQ(kTestETag, http_request_.headers["If-Match"]); 1470 1471 // 2) Upload the content to the upload URL. 1472 UploadRangeResponse response; 1473 scoped_ptr<ResourceEntry> new_entry; 1474 1475 { 1476 base::RunLoop run_loop; 1477 ResumeUploadRequest* resume_request = new ResumeUploadRequest( 1478 request_sender_.get(), 1479 test_util::CreateQuitCallback( 1480 &run_loop, 1481 test_util::CreateCopyResultCallback(&response, &new_entry)), 1482 ProgressCallback(), 1483 upload_url, 1484 0, // start_position 1485 kUploadContent.size(), // end_position (exclusive) 1486 kUploadContent.size(), // content_length, 1487 "text/plain", // content_type 1488 kTestFilePath); 1489 request_sender_->StartRequestWithRetry(resume_request); 1490 run_loop.Run(); 1491 } 1492 1493 // METHOD_PUT should be used to upload data. 1494 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1495 // Request should go to the upload URL. 1496 EXPECT_EQ(upload_url.path(), http_request_.relative_url); 1497 // Content-Range header should be added. 1498 EXPECT_EQ("bytes 0-" + 1499 base::Int64ToString(kUploadContent.size() -1) + "/" + 1500 base::Int64ToString(kUploadContent.size()), 1501 http_request_.headers["Content-Range"]); 1502 // The upload content should be set in the HTTP request. 1503 EXPECT_TRUE(http_request_.has_content); 1504 EXPECT_EQ(kUploadContent, http_request_.content); 1505 1506 // Check the response. 1507 EXPECT_EQ(HTTP_SUCCESS, response.code); // Because it's an existing file. 1508 // The start and end positions should be set to -1, if an upload is complete. 1509 EXPECT_EQ(-1, response.start_position_received); 1510 EXPECT_EQ(-1, response.end_position_received); 1511 } 1512 1513 // This test exercises InitiateUploadExistingFileRequest for a scenario of 1514 // confliction on updating an existing file. 1515 TEST_F(GDataWapiRequestsTest, UploadExistingFileWithETagConflict) { 1516 const std::string kUploadContent = "hello"; 1517 const std::string kWrongETag = "wrong_etag"; 1518 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1519 GURL upload_url; 1520 1521 { 1522 base::RunLoop run_loop; 1523 InitiateUploadExistingFileRequest* initiate_request = 1524 new InitiateUploadExistingFileRequest( 1525 request_sender_.get(), 1526 *url_generator_, 1527 test_util::CreateQuitCallback( 1528 &run_loop, 1529 test_util::CreateCopyResultCallback(&result_code, &upload_url)), 1530 "text/plain", 1531 kUploadContent.size(), 1532 "file:foo", 1533 kWrongETag); 1534 request_sender_->StartRequestWithRetry(initiate_request); 1535 run_loop.Run(); 1536 } 1537 1538 EXPECT_EQ(HTTP_PRECONDITION, result_code); 1539 // For updating an existing file, METHOD_PUT should be used. 1540 EXPECT_EQ(net::test_server::METHOD_PUT, http_request_.method); 1541 // convert=false should be passed as files should be uploaded as-is. 1542 EXPECT_EQ("/feeds/upload/create-session/default/private/full/file%3Afoo" 1543 "?convert=false&v=3&alt=json&showroot=true", 1544 http_request_.relative_url); 1545 // Even though the body is empty, the content type should be set to 1546 // "text/plain". 1547 EXPECT_EQ("text/plain", http_request_.headers["Content-Type"]); 1548 EXPECT_EQ("text/plain", http_request_.headers["X-Upload-Content-Type"]); 1549 EXPECT_EQ(base::Int64ToString(kUploadContent.size()), 1550 http_request_.headers["X-Upload-Content-Length"]); 1551 // For updating an existing file, an empty body should be attached (PUT 1552 // requires a body) 1553 EXPECT_TRUE(http_request_.has_content); 1554 EXPECT_EQ("", http_request_.content); 1555 EXPECT_EQ(kWrongETag, http_request_.headers["If-Match"]); 1556 } 1557 1558 TEST_F(GDataWapiRequestsTest, DownloadFileRequest) { 1559 const base::FilePath kDownloadedFilePath = 1560 temp_dir_.path().AppendASCII("cache_file"); 1561 const std::string kTestIdWithTypeLabel("file:dummyId"); 1562 const std::string kTestId("dummyId"); 1563 1564 GDataErrorCode result_code = GDATA_OTHER_ERROR; 1565 base::FilePath temp_file; 1566 { 1567 base::RunLoop run_loop; 1568 DownloadFileRequest* request = new DownloadFileRequest( 1569 request_sender_.get(), 1570 *url_generator_, 1571 test_util::CreateQuitCallback( 1572 &run_loop, 1573 test_util::CreateCopyResultCallback(&result_code, &temp_file)), 1574 GetContentCallback(), 1575 ProgressCallback(), 1576 kTestIdWithTypeLabel, 1577 kDownloadedFilePath); 1578 request_sender_->StartRequestWithRetry(request); 1579 run_loop.Run(); 1580 } 1581 1582 std::string contents; 1583 file_util::ReadFileToString(temp_file, &contents); 1584 base::DeleteFile(temp_file, false); 1585 1586 EXPECT_EQ(HTTP_SUCCESS, result_code); 1587 EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method); 1588 EXPECT_EQ(kTestDownloadPathPrefix + kTestId, http_request_.relative_url); 1589 EXPECT_EQ(kDownloadedFilePath, temp_file); 1590 1591 const std::string expected_contents = kTestId + kTestId + kTestId; 1592 EXPECT_EQ(expected_contents, contents); 1593 } 1594 1595 } // namespace google_apis 1596