// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // A complete set of unit tests for GaiaOAuthClient. #include #include #include "base/json/json_reader.h" #include "base/macros.h" #include "base/strings/string_number_conversions.h" #include "base/values.h" #include "google_apis/gaia/gaia_oauth_client.h" #include "net/base/net_errors.h" #include "net/http/http_status_code.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_fetcher_delegate.h" #include "net/url_request/url_request_status.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" using ::testing::_; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Pointee; using ::testing::SaveArg; namespace { // Responds as though OAuth returned from the server. class MockOAuthFetcher : public net::TestURLFetcher { public: MockOAuthFetcher(int response_code, int max_failure_count, bool complete_immediately, const GURL& url, const std::string& results, net::URLFetcher::RequestType request_type, net::URLFetcherDelegate* d) : net::TestURLFetcher(0, url, d), max_failure_count_(max_failure_count), current_failure_count_(0), complete_immediately_(complete_immediately) { set_url(url); set_response_code(response_code); SetResponseString(results); } ~MockOAuthFetcher() override {} void Start() override { if ((GetResponseCode() != net::HTTP_OK) && (max_failure_count_ != -1) && (current_failure_count_ == max_failure_count_)) { set_response_code(net::HTTP_OK); } net::Error error = net::OK; if (GetResponseCode() != net::HTTP_OK) { error = net::ERR_FAILED; current_failure_count_++; } set_status(net::URLRequestStatus::FromError(error)); if (complete_immediately_) delegate()->OnURLFetchComplete(this); } void Finish() { ASSERT_FALSE(complete_immediately_); delegate()->OnURLFetchComplete(this); } private: int max_failure_count_; int current_failure_count_; bool complete_immediately_; DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcher); }; class MockOAuthFetcherFactory : public net::URLFetcherFactory, public net::ScopedURLFetcherFactory { public: MockOAuthFetcherFactory() : net::ScopedURLFetcherFactory(this), response_code_(net::HTTP_OK), complete_immediately_(true) { } ~MockOAuthFetcherFactory() override {} scoped_ptr CreateURLFetcher( int id, const GURL& url, net::URLFetcher::RequestType request_type, net::URLFetcherDelegate* d) override { url_fetcher_ = new MockOAuthFetcher( response_code_, max_failure_count_, complete_immediately_, url, results_, request_type, d); return scoped_ptr(url_fetcher_); } void set_response_code(int response_code) { response_code_ = response_code; } void set_max_failure_count(int count) { max_failure_count_ = count; } void set_results(const std::string& results) { results_ = results; } MockOAuthFetcher* get_url_fetcher() { return url_fetcher_; } void set_complete_immediately(bool complete_immediately) { complete_immediately_ = complete_immediately; } private: MockOAuthFetcher* url_fetcher_; int response_code_; bool complete_immediately_; int max_failure_count_; std::string results_; DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcherFactory); }; const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg"; const std::string kTestAccessTokenHandle = "1/kjhH87dfgkj87Hhj5KJkjZ"; const std::string kTestRefreshToken = "1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ"; const std::string kTestUserEmail = "a_user@gmail.com"; const std::string kTestUserId = "8675309"; const int kTestExpiresIn = 3920; const std::string kDummyGetTokensResult = "{\"access_token\":\"" + kTestAccessToken + "\"," "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "," "\"refresh_token\":\"" + kTestRefreshToken + "\"}"; const std::string kDummyRefreshTokenResult = "{\"access_token\":\"" + kTestAccessToken + "\"," "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}"; const std::string kDummyUserInfoResult = "{\"email\":\"" + kTestUserEmail + "\"}"; const std::string kDummyUserIdResult = "{\"id\":\"" + kTestUserId + "\"}"; const std::string kDummyFullUserInfoResult = "{" "\"family_name\": \"Bar\", " "\"name\": \"Foo Bar\", " "\"picture\": \"https://lh4.googleusercontent.com/hash/photo.jpg\", " "\"locale\": \"en\", " "\"gender\": \"male\", " "\"link\": \"https://plus.google.com/+FooBar\", " "\"given_name\": \"Foo\", " "\"id\": \"12345678901234567890\"" "}"; const std::string kDummyTokenInfoResult = "{\"issued_to\": \"1234567890.apps.googleusercontent.com\"," "\"audience\": \"1234567890.apps.googleusercontent.com\"," "\"scope\": \"https://googleapis.com/oauth2/v2/tokeninfo\"," "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}"; const std::string kDummyTokenHandleInfoResult = "{\"audience\": \"1234567890.apps.googleusercontent.com\"," "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}"; } // namespace namespace gaia { class GaiaOAuthClientTest : public testing::Test { protected: void SetUp() override { client_info_.client_id = "test_client_id"; client_info_.client_secret = "test_client_secret"; client_info_.redirect_uri = "test_redirect_uri"; }; protected: net::TestURLRequestContextGetter* GetRequestContext() { if (!request_context_getter_.get()) { request_context_getter_ = new net::TestURLRequestContextGetter( message_loop_.task_runner()); } return request_context_getter_.get(); } base::MessageLoop message_loop_; scoped_refptr request_context_getter_; OAuthClientInfo client_info_; }; class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate { public: MockGaiaOAuthClientDelegate() {} ~MockGaiaOAuthClientDelegate() {} MOCK_METHOD3(OnGetTokensResponse, void(const std::string& refresh_token, const std::string& access_token, int expires_in_seconds)); MOCK_METHOD2(OnRefreshTokenResponse, void(const std::string& access_token, int expires_in_seconds)); MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email)); MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id)); MOCK_METHOD0(OnOAuthError, void()); MOCK_METHOD1(OnNetworkError, void(int response_code)); // gMock doesn't like methods that take or return scoped_ptr. A // work-around is to create a mock method that takes a raw ptr, and // override the problematic method to call through to it. // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ MOCK_METHOD1(OnGetUserInfoResponsePtr, void(const base::DictionaryValue* user_info)); void OnGetUserInfoResponse( scoped_ptr user_info) override { user_info_.reset(user_info.release()); OnGetUserInfoResponsePtr(user_info_.get()); } MOCK_METHOD1(OnGetTokenInfoResponsePtr, void(const base::DictionaryValue* token_info)); void OnGetTokenInfoResponse( scoped_ptr token_info) override { token_info_.reset(token_info.release()); OnGetTokenInfoResponsePtr(token_info_.get()); } private: scoped_ptr user_info_; scoped_ptr token_info_; DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate); }; TEST_F(GaiaOAuthClientTest, NetworkFailure) { int response_code = net::HTTP_INTERNAL_SERVER_ERROR; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnNetworkError(response_code)) .Times(1); MockOAuthFetcherFactory factory; factory.set_response_code(response_code); factory.set_max_failure_count(4); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &delegate); } TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) { int response_code = net::HTTP_INTERNAL_SERVER_ERROR; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken, kTestExpiresIn)).Times(1); MockOAuthFetcherFactory factory; factory.set_response_code(response_code); factory.set_max_failure_count(4); factory.set_results(kDummyGetTokensResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); } TEST_F(GaiaOAuthClientTest, OAuthFailure) { int response_code = net::HTTP_BAD_REQUEST; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnOAuthError()).Times(1); MockOAuthFetcherFactory factory; factory.set_response_code(response_code); factory.set_max_failure_count(-1); factory.set_results(kDummyGetTokensResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); } TEST_F(GaiaOAuthClientTest, GetTokensSuccess) { MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken, kTestExpiresIn)).Times(1); MockOAuthFetcherFactory factory; factory.set_results(kDummyGetTokensResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); } TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) { MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken, kTestExpiresIn)).Times(1); MockOAuthFetcherFactory factory; factory.set_results(kDummyRefreshTokenResult); factory.set_complete_immediately(false); GaiaOAuthClient auth(GetRequestContext()); auth.RefreshToken(client_info_, "refresh_token", std::vector(), -1, &delegate); EXPECT_THAT(factory.get_url_fetcher()->upload_data(), Not(HasSubstr("scope"))); factory.get_url_fetcher()->Finish(); } TEST_F(GaiaOAuthClientTest, RefreshTokenDownscopingSuccess) { MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken, kTestExpiresIn)).Times(1); MockOAuthFetcherFactory factory; factory.set_results(kDummyRefreshTokenResult); factory.set_complete_immediately(false); GaiaOAuthClient auth(GetRequestContext()); auth.RefreshToken(client_info_, "refresh_token", std::vector(1, "scope4test"), -1, &delegate); EXPECT_THAT(factory.get_url_fetcher()->upload_data(), HasSubstr("&scope=scope4test")); factory.get_url_fetcher()->Finish(); } TEST_F(GaiaOAuthClientTest, GetUserEmail) { MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetUserEmailResponse(kTestUserEmail)).Times(1); MockOAuthFetcherFactory factory; factory.set_results(kDummyUserInfoResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetUserEmail("access_token", 1, &delegate); } TEST_F(GaiaOAuthClientTest, GetUserId) { MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetUserIdResponse(kTestUserId)).Times(1); MockOAuthFetcherFactory factory; factory.set_results(kDummyUserIdResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetUserId("access_token", 1, &delegate); } TEST_F(GaiaOAuthClientTest, GetUserInfo) { const base::DictionaryValue* captured_result; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetUserInfoResponsePtr(_)) .WillOnce(SaveArg<0>(&captured_result)); MockOAuthFetcherFactory factory; factory.set_results(kDummyFullUserInfoResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetUserInfo("access_token", 1, &delegate); scoped_ptr value = base::JSONReader::Read(kDummyFullUserInfoResult); DCHECK(value); ASSERT_TRUE(value->IsType(base::Value::TYPE_DICTIONARY)); base::DictionaryValue* expected_result; value->GetAsDictionary(&expected_result); ASSERT_TRUE(expected_result->Equals(captured_result)); } TEST_F(GaiaOAuthClientTest, GetTokenInfo) { const base::DictionaryValue* captured_result; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_)) .WillOnce(SaveArg<0>(&captured_result)); MockOAuthFetcherFactory factory; factory.set_results(kDummyTokenInfoResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokenInfo("some_token", 1, &delegate); std::string issued_to; ASSERT_TRUE(captured_result->GetString("issued_to", &issued_to)); ASSERT_EQ("1234567890.apps.googleusercontent.com", issued_to); } TEST_F(GaiaOAuthClientTest, GetTokenHandleInfo) { const base::DictionaryValue* captured_result; MockGaiaOAuthClientDelegate delegate; EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_)) .WillOnce(SaveArg<0>(&captured_result)); MockOAuthFetcherFactory factory; factory.set_results(kDummyTokenHandleInfoResult); GaiaOAuthClient auth(GetRequestContext()); auth.GetTokenHandleInfo("some_handle", 1, &delegate); std::string audience; ASSERT_TRUE(captured_result->GetString("audience", &audience)); ASSERT_EQ("1234567890.apps.googleusercontent.com", audience); } } // namespace gaia