Index: sync/internal_api/attachments/attachment_uploader_impl_unittest.cc |
diff --git a/sync/internal_api/attachments/attachment_uploader_impl_unittest.cc b/sync/internal_api/attachments/attachment_uploader_impl_unittest.cc |
index 99c2247f972b494fa704e4cf1e93455ab13ea9b4..f2ad7ce96fe4aa91393242e5e788aa8c3ef16bbc 100644 |
--- a/sync/internal_api/attachments/attachment_uploader_impl_unittest.cc |
+++ b/sync/internal_api/attachments/attachment_uploader_impl_unittest.cc |
@@ -11,19 +11,28 @@ |
#include "base/message_loop/message_loop.h" |
#include "base/run_loop.h" |
#include "base/strings/stringprintf.h" |
+#include "base/synchronization/lock.h" |
+#include "base/thread_task_runner_handle.h" |
#include "base/threading/non_thread_safe.h" |
#include "base/threading/thread.h" |
+#include "google_apis/gaia/fake_oauth2_token_service.h" |
+#include "google_apis/gaia/gaia_constants.h" |
+#include "google_apis/gaia/oauth2_token_service_request.h" |
#include "net/test/embedded_test_server/embedded_test_server.h" |
#include "net/test/embedded_test_server/http_request.h" |
#include "net/test/embedded_test_server/http_response.h" |
#include "net/url_request/url_request_test_util.h" |
#include "sync/api/attachments/attachment.h" |
#include "sync/protocol/sync.pb.h" |
+#include "testing/gmock/include/gmock/gmock-matchers.h" |
#include "testing/gtest/include/gtest/gtest.h" |
namespace { |
const char kAttachmentData[] = "some data"; |
+const char kAccountId[] = "some-account-id"; |
+const char kAccessToken[] = "some-access-token"; |
+const char kAuthorization[] = "Authorization"; |
} // namespace |
@@ -35,10 +44,131 @@ using net::test_server::HttpResponse; |
class RequestHandler; |
+// A mock implementation of an OAuth2TokenService. |
+// |
+// Use |SetResponse| to vary the response to token requests. |
+// |
+// Use |num_invalidate_token| and |last_token_invalidated| to check the number |
+// of invalidate token operations performed and the last token invalidated. |
+class MockOAuth2TokenService : public FakeOAuth2TokenService { |
+ public: |
+ MockOAuth2TokenService(); |
+ virtual ~MockOAuth2TokenService(); |
+ |
+ void SetResponse(const GoogleServiceAuthError& error, |
+ const std::string& access_token, |
+ const base::Time& expiration); |
+ |
+ int num_invalidate_token() const { return num_invalidate_token_; } |
+ |
+ const std::string& last_token_invalidated() const { |
+ return last_token_invalidated_; |
+ } |
+ |
+ protected: |
+ virtual void FetchOAuth2Token(RequestImpl* request, |
+ const std::string& account_id, |
+ net::URLRequestContextGetter* getter, |
+ const std::string& client_id, |
+ const std::string& client_secret, |
+ const ScopeSet& scopes) OVERRIDE; |
+ |
+ virtual void InvalidateOAuth2Token(const std::string& account_id, |
+ const std::string& client_id, |
+ const ScopeSet& scopes, |
+ const std::string& access_token) OVERRIDE; |
+ |
+ private: |
+ GoogleServiceAuthError response_error_; |
+ std::string response_access_token_; |
+ base::Time response_expiration_; |
+ int num_invalidate_token_; |
+ std::string last_token_invalidated_; |
+}; |
+ |
+MockOAuth2TokenService::MockOAuth2TokenService() |
+ : response_error_(GoogleServiceAuthError::AuthErrorNone()), |
+ response_access_token_(kAccessToken), |
+ response_expiration_(base::Time::Max()), |
+ num_invalidate_token_(0) { |
+} |
+ |
+MockOAuth2TokenService::~MockOAuth2TokenService() { |
+} |
+ |
+void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError& error, |
+ const std::string& access_token, |
+ const base::Time& expiration) { |
+ response_error_ = error; |
+ response_access_token_ = access_token; |
+ response_expiration_ = expiration; |
+} |
+ |
+void MockOAuth2TokenService::FetchOAuth2Token( |
+ RequestImpl* request, |
+ const std::string& account_id, |
+ net::URLRequestContextGetter* getter, |
+ const std::string& client_id, |
+ const std::string& client_secret, |
+ const ScopeSet& scopes) { |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer, |
+ request->AsWeakPtr(), |
+ response_error_, |
+ response_access_token_, |
+ response_expiration_)); |
+} |
+ |
+void MockOAuth2TokenService::InvalidateOAuth2Token( |
+ const std::string& account_id, |
+ const std::string& client_id, |
+ const ScopeSet& scopes, |
+ const std::string& access_token) { |
+ ++num_invalidate_token_; |
+ last_token_invalidated_ = access_token; |
+} |
+ |
+class TokenServiceProvider |
+ : public OAuth2TokenServiceRequest::TokenServiceProvider, |
+ base::NonThreadSafe { |
+ public: |
+ TokenServiceProvider(OAuth2TokenService* token_service); |
+ virtual ~TokenServiceProvider(); |
+ |
+ // OAuth2TokenService::TokenServiceProvider implementation. |
+ virtual scoped_refptr<base::SingleThreadTaskRunner> |
+ GetTokenServiceTaskRunner() OVERRIDE; |
+ virtual OAuth2TokenService* GetTokenService() OVERRIDE; |
+ |
+ private: |
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
+ OAuth2TokenService* token_service_; |
+}; |
+ |
+TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service) |
+ : task_runner_(base::ThreadTaskRunnerHandle::Get()), |
+ token_service_(token_service) { |
+ DCHECK(token_service_); |
+} |
+ |
+TokenServiceProvider::~TokenServiceProvider() { |
+} |
+ |
+scoped_refptr<base::SingleThreadTaskRunner> |
+TokenServiceProvider::GetTokenServiceTaskRunner() { |
+ return task_runner_; |
+} |
+ |
+OAuth2TokenService* TokenServiceProvider::GetTokenService() { |
+ DCHECK(task_runner_->BelongsToCurrentThread()); |
+ return token_service_; |
+} |
+ |
// Text fixture for AttachmentUploaderImpl test. |
// |
-// This fixture provides an embedded HTTP server for interacting with |
-// AttachmentUploaderImpl. |
+// This fixture provides an embedded HTTP server and a mock OAuth2 token service |
+// for interacting with AttachmentUploaderImpl |
class AttachmentUploaderImplTest : public testing::Test, |
public base::NonThreadSafe { |
public: |
@@ -47,6 +177,7 @@ class AttachmentUploaderImplTest : public testing::Test, |
protected: |
AttachmentUploaderImplTest(); |
virtual void SetUp(); |
+ virtual void TearDown(); |
// Run the message loop until UploadDone has been invoked |num_uploads| times. |
void RunAndWaitFor(int num_uploads); |
@@ -56,6 +187,9 @@ class AttachmentUploaderImplTest : public testing::Test, |
std::vector<HttpRequest>& http_requests_received(); |
std::vector<AttachmentUploader::UploadResult>& upload_results(); |
std::vector<AttachmentId>& updated_attachment_ids(); |
+ MockOAuth2TokenService& token_service(); |
+ base::MessageLoopForIO& message_loop(); |
+ RequestHandler& request_handler(); |
private: |
// An UploadCallback invoked by AttachmentUploaderImpl. |
@@ -68,18 +202,20 @@ class AttachmentUploaderImplTest : public testing::Test, |
scoped_ptr<AttachmentUploader> uploader_; |
AttachmentUploader::UploadCallback upload_callback_; |
net::test_server::EmbeddedTestServer server_; |
- |
// A closure that signals an upload has finished. |
base::Closure signal_upload_done_; |
std::vector<HttpRequest> http_requests_received_; |
std::vector<AttachmentUploader::UploadResult> upload_results_; |
std::vector<AttachmentId> updated_attachment_ids_; |
+ scoped_ptr<MockOAuth2TokenService> token_service_; |
// Must be last data member. |
base::WeakPtrFactory<AttachmentUploaderImplTest> weak_ptr_factory_; |
}; |
// Handles HTTP requests received by the EmbeddedTestServer. |
+// |
+// Responds with HTTP_OK by default. See |SetStatusCode|. |
class RequestHandler : public base::NonThreadSafe { |
public: |
// Construct a RequestHandler that will PostTask to |test| using |
@@ -92,7 +228,17 @@ class RequestHandler : public base::NonThreadSafe { |
scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request); |
+ // Set the HTTP status code to respond with. |
+ void SetStatusCode(const net::HttpStatusCode& status_code); |
+ |
+ // Returns the HTTP status code that will be used in responses. |
+ net::HttpStatusCode GetStatusCode() const; |
+ |
private: |
+ // Protects status_code_. |
+ mutable base::Lock mutex_; |
+ net::HttpStatusCode status_code_; |
+ |
scoped_refptr<base::SingleThreadTaskRunner> test_task_runner_; |
base::WeakPtr<AttachmentUploaderImplTest> test_; |
}; |
@@ -121,12 +267,26 @@ void AttachmentUploaderImplTest::SetUp() { |
std::string url_prefix( |
base::StringPrintf("http://localhost:%d/uploads/", server_.port())); |
- uploader().reset( |
- new AttachmentUploaderImpl(url_prefix, url_request_context_getter_)); |
+ token_service_.reset(new MockOAuth2TokenService); |
+ scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider> |
+ token_service_provider(new TokenServiceProvider(token_service_.get())); |
+ |
+ OAuth2TokenService::ScopeSet scopes; |
+ scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); |
+ uploader().reset(new AttachmentUploaderImpl(url_prefix, |
+ url_request_context_getter_, |
+ kAccountId, |
+ scopes, |
+ token_service_provider.Pass())); |
+ |
upload_callback_ = base::Bind(&AttachmentUploaderImplTest::UploadDone, |
base::Unretained(this)); |
} |
+void AttachmentUploaderImplTest::TearDown() { |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
void AttachmentUploaderImplTest::RunAndWaitFor(int num_uploads) { |
for (int i = 0; i < num_uploads; ++i) { |
// Run the loop until one upload completes. |
@@ -159,6 +319,18 @@ AttachmentUploaderImplTest::updated_attachment_ids() { |
return updated_attachment_ids_; |
} |
+MockOAuth2TokenService& AttachmentUploaderImplTest::token_service() { |
+ return *token_service_; |
+} |
+ |
+base::MessageLoopForIO& AttachmentUploaderImplTest::message_loop() { |
+ return message_loop_; |
+} |
+ |
+RequestHandler& AttachmentUploaderImplTest::request_handler() { |
+ return *request_handler_; |
+} |
+ |
void AttachmentUploaderImplTest::UploadDone( |
const AttachmentUploader::UploadResult& result, |
const AttachmentId& updated_attachment_id) { |
@@ -171,7 +343,9 @@ void AttachmentUploaderImplTest::UploadDone( |
RequestHandler::RequestHandler( |
const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner, |
const base::WeakPtr<AttachmentUploaderImplTest>& test) |
- : test_task_runner_(test_task_runner), test_(test) { |
+ : status_code_(net::HTTP_OK), |
+ test_task_runner_(test_task_runner), |
+ test_(test) { |
DetachFromThread(); |
} |
@@ -186,23 +360,46 @@ scoped_ptr<HttpResponse> RequestHandler::HandleRequest( |
FROM_HERE, |
base::Bind( |
&AttachmentUploaderImplTest::OnRequestReceived, test_, request)); |
- scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse); |
- http_response->set_code(net::HTTP_OK); |
- http_response->set_content("hello"); |
- http_response->set_content_type("text/plain"); |
- return http_response.PassAs<HttpResponse>(); |
+ scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse); |
+ response->set_code(GetStatusCode()); |
+ response->set_content_type("text/plain"); |
+ return response.PassAs<HttpResponse>(); |
+} |
+ |
+void RequestHandler::SetStatusCode(const net::HttpStatusCode& status_code) { |
+ base::AutoLock lock(mutex_); |
+ status_code_ = status_code; |
+} |
+ |
+net::HttpStatusCode RequestHandler::GetStatusCode() const { |
+ base::AutoLock lock(mutex_); |
+ return status_code_; |
} |
// Verify the "happy case" of uploading an attachment. |
+// |
+// Token is requested, token is returned, HTTP request is made, attachment is |
+// received by server. |
TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) { |
+ token_service().AddAccount(kAccountId); |
+ request_handler().SetStatusCode(net::HTTP_OK); |
+ |
scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
some_data->data() = kAttachmentData; |
Attachment attachment = Attachment::Create(some_data); |
uploader()->UploadAttachment(attachment, upload_callback()); |
+ |
+ // Run until the done callback is invoked. |
RunAndWaitFor(1); |
+ // See that the done callback was invoked with the right arguments. |
+ ASSERT_EQ(1U, upload_results().size()); |
+ EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results()[0]); |
+ ASSERT_EQ(1U, updated_attachment_ids().size()); |
+ EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); |
+ |
// See that the HTTP server received one request. |
- EXPECT_EQ(1U, http_requests_received().size()); |
+ ASSERT_EQ(1U, http_requests_received().size()); |
const HttpRequest& http_request = http_requests_received().front(); |
EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); |
std::string expected_relative_url("/uploads/" + |
@@ -210,12 +407,11 @@ TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) { |
EXPECT_EQ(expected_relative_url, http_request.relative_url); |
EXPECT_TRUE(http_request.has_content); |
EXPECT_EQ(kAttachmentData, http_request.content); |
+ const std::string header_name(kAuthorization); |
+ const std::string header_value(std::string("Bearer ") + kAccessToken); |
+ EXPECT_THAT(http_request.headers, |
+ testing::Contains(testing::Pair(header_name, header_value))); |
- // See that the UploadCallback received a result and updated AttachmentId. |
- EXPECT_EQ(1U, upload_results().size()); |
- EXPECT_EQ(1U, updated_attachment_ids().size()); |
- EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results().front()); |
- EXPECT_EQ(attachment.GetId(), updated_attachment_ids().front()); |
// TODO(maniscalco): Once AttachmentUploaderImpl is capable of updating the |
// AttachmentId with server address information about the attachment, add some |
// checks here to verify it works properly (bug 371522). |
@@ -224,12 +420,17 @@ TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) { |
// Verify two overlapping calls to upload the same attachment result in only one |
// HTTP request. |
TEST_F(AttachmentUploaderImplTest, UploadAttachment_Collapse) { |
+ token_service().AddAccount(kAccountId); |
+ request_handler().SetStatusCode(net::HTTP_OK); |
+ |
scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
some_data->data() = kAttachmentData; |
Attachment attachment1 = Attachment::Create(some_data); |
Attachment attachment2 = attachment1; |
uploader()->UploadAttachment(attachment1, upload_callback()); |
uploader()->UploadAttachment(attachment2, upload_callback()); |
+ base::RunLoop().RunUntilIdle(); |
+ |
// Wait for upload_callback() to be invoked twice. |
RunAndWaitFor(2); |
// See there was only one request. |
@@ -240,18 +441,127 @@ TEST_F(AttachmentUploaderImplTest, UploadAttachment_Collapse) { |
// uplaod finishes. We do this by issuing two non-overlapping uploads for the |
// same attachment and see that it results in two HTTP requests. |
TEST_F(AttachmentUploaderImplTest, UploadAttachment_CleanUpAfterUpload) { |
+ token_service().AddAccount(kAccountId); |
+ request_handler().SetStatusCode(net::HTTP_OK); |
+ |
scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
some_data->data() = kAttachmentData; |
Attachment attachment1 = Attachment::Create(some_data); |
Attachment attachment2 = attachment1; |
uploader()->UploadAttachment(attachment1, upload_callback()); |
+ base::RunLoop().RunUntilIdle(); |
+ |
// Wait for upload_callback() to be invoked before starting the second upload. |
RunAndWaitFor(1); |
uploader()->UploadAttachment(attachment2, upload_callback()); |
+ base::RunLoop().RunUntilIdle(); |
+ |
// Wait for upload_callback() to be invoked a second time. |
RunAndWaitFor(1); |
// See there were two requests. |
- EXPECT_EQ(2U, http_requests_received().size()); |
+ ASSERT_EQ(2U, http_requests_received().size()); |
+} |
+ |
+// Verify that we do not issue an HTTP request when we fail to receive an access |
+// token. |
+// |
+// Token is requested, no token is returned, no HTTP request is made |
+TEST_F(AttachmentUploaderImplTest, UploadAttachment_FailToGetToken) { |
+ // Note, we won't receive a token because we did not add kAccountId to the |
+ // token service. |
+ scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
+ some_data->data() = kAttachmentData; |
+ Attachment attachment = Attachment::Create(some_data); |
+ uploader()->UploadAttachment(attachment, upload_callback()); |
+ |
+ RunAndWaitFor(1); |
+ |
+ // See that the done callback was invoked. |
+ ASSERT_EQ(1U, upload_results().size()); |
+ EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); |
+ ASSERT_EQ(1U, updated_attachment_ids().size()); |
+ EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); |
+ |
+ // See that no HTTP request was received. |
+ ASSERT_EQ(0U, http_requests_received().size()); |
+} |
+ |
+// Verify behavior when the server returns "503 Service Unavailable". |
+TEST_F(AttachmentUploaderImplTest, UploadAttachment_ServiceUnavilable) { |
+ token_service().AddAccount(kAccountId); |
+ request_handler().SetStatusCode(net::HTTP_SERVICE_UNAVAILABLE); |
+ |
+ scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
+ some_data->data() = kAttachmentData; |
+ Attachment attachment = Attachment::Create(some_data); |
+ uploader()->UploadAttachment(attachment, upload_callback()); |
+ |
+ RunAndWaitFor(1); |
+ |
+ // See that the done callback was invoked. |
+ ASSERT_EQ(1U, upload_results().size()); |
+ EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); |
+ ASSERT_EQ(1U, updated_attachment_ids().size()); |
+ EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); |
+ |
+ // See that the HTTP server received one request. |
+ ASSERT_EQ(1U, http_requests_received().size()); |
+ const HttpRequest& http_request = http_requests_received().front(); |
+ EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); |
+ std::string expected_relative_url("/uploads/" + |
+ attachment.GetId().GetProto().unique_id()); |
+ EXPECT_EQ(expected_relative_url, http_request.relative_url); |
+ EXPECT_TRUE(http_request.has_content); |
+ EXPECT_EQ(kAttachmentData, http_request.content); |
+ std::string expected_header(kAuthorization); |
+ const std::string header_name(kAuthorization); |
+ const std::string header_value(std::string("Bearer ") + kAccessToken); |
+ EXPECT_THAT(http_request.headers, |
+ testing::Contains(testing::Pair(header_name, header_value))); |
+ |
+ // See that we did not invalidate the token. |
+ ASSERT_EQ(0, token_service().num_invalidate_token()); |
} |
+// Verify that when we receive an "401 Unauthorized" we invalidate the access |
+// token. |
+TEST_F(AttachmentUploaderImplTest, UploadAttachment_BadToken) { |
+ token_service().AddAccount(kAccountId); |
+ request_handler().SetStatusCode(net::HTTP_UNAUTHORIZED); |
+ |
+ scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString); |
+ some_data->data() = kAttachmentData; |
+ Attachment attachment = Attachment::Create(some_data); |
+ uploader()->UploadAttachment(attachment, upload_callback()); |
+ |
+ RunAndWaitFor(1); |
+ |
+ // See that the done callback was invoked. |
+ ASSERT_EQ(1U, upload_results().size()); |
+ EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]); |
+ ASSERT_EQ(1U, updated_attachment_ids().size()); |
+ EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]); |
+ |
+ // See that the HTTP server received one request. |
+ ASSERT_EQ(1U, http_requests_received().size()); |
+ const HttpRequest& http_request = http_requests_received().front(); |
+ EXPECT_EQ(net::test_server::METHOD_POST, http_request.method); |
+ std::string expected_relative_url("/uploads/" + |
+ attachment.GetId().GetProto().unique_id()); |
+ EXPECT_EQ(expected_relative_url, http_request.relative_url); |
+ EXPECT_TRUE(http_request.has_content); |
+ EXPECT_EQ(kAttachmentData, http_request.content); |
+ std::string expected_header(kAuthorization); |
+ const std::string header_name(kAuthorization); |
+ const std::string header_value(std::string("Bearer ") + kAccessToken); |
+ EXPECT_THAT(http_request.headers, |
+ testing::Contains(testing::Pair(header_name, header_value))); |
+ |
+ // See that we invalidated the token. |
+ ASSERT_EQ(1, token_service().num_invalidate_token()); |
+} |
+ |
+// TODO(maniscalco): Add test case for when we are uploading an attachment that |
+// already exists. 409 Conflict? (bug 379825) |
+ |
} // namespace syncer |