| 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..f0cf98c7c528bd4419ceba860313bc4ae910a5c2 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:
|
| @@ -56,6 +186,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 +201,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 +227,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,8 +266,15 @@ 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()));
|
| +
|
| + uploader().reset(new AttachmentUploaderImpl(url_prefix,
|
| + url_request_context_getter_,
|
| + kAccountId,
|
| + token_service_provider.Pass()));
|
| +
|
| upload_callback_ = base::Bind(&AttachmentUploaderImplTest::UploadDone,
|
| base::Unretained(this));
|
| }
|
| @@ -159,6 +311,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 +335,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 +352,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 +399,12 @@ 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);
|
| + 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 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 +413,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 +434,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
|
|
|