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 // A complete set of unit tests for GaiaOAuthClient. 6 7 #include <string> 8 #include <vector> 9 10 #include "base/json/json_reader.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/values.h" 13 #include "google_apis/gaia/gaia_oauth_client.h" 14 #include "net/base/net_errors.h" 15 #include "net/http/http_status_code.h" 16 #include "net/url_request/test_url_fetcher_factory.h" 17 #include "net/url_request/url_fetcher_delegate.h" 18 #include "net/url_request/url_request_status.h" 19 #include "net/url_request/url_request_test_util.h" 20 #include "testing/gmock/include/gmock/gmock.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 #include "url/gurl.h" 23 24 using ::testing::_; 25 using ::testing::Eq; 26 using ::testing::HasSubstr; 27 using ::testing::Pointee; 28 using ::testing::SaveArg; 29 30 namespace { 31 32 // Responds as though OAuth returned from the server. 33 class MockOAuthFetcher : public net::TestURLFetcher { 34 public: 35 MockOAuthFetcher(int response_code, 36 int max_failure_count, 37 bool complete_immediately, 38 const GURL& url, 39 const std::string& results, 40 net::URLFetcher::RequestType request_type, 41 net::URLFetcherDelegate* d) 42 : net::TestURLFetcher(0, url, d), 43 max_failure_count_(max_failure_count), 44 current_failure_count_(0), 45 complete_immediately_(complete_immediately) { 46 set_url(url); 47 set_response_code(response_code); 48 SetResponseString(results); 49 } 50 51 virtual ~MockOAuthFetcher() { } 52 53 virtual void Start() OVERRIDE { 54 if ((GetResponseCode() != net::HTTP_OK) && (max_failure_count_ != -1) && 55 (current_failure_count_ == max_failure_count_)) { 56 set_response_code(net::HTTP_OK); 57 } 58 59 net::URLRequestStatus::Status code = net::URLRequestStatus::SUCCESS; 60 if (GetResponseCode() != net::HTTP_OK) { 61 code = net::URLRequestStatus::FAILED; 62 current_failure_count_++; 63 } 64 set_status(net::URLRequestStatus(code, 0)); 65 66 if (complete_immediately_) 67 delegate()->OnURLFetchComplete(this); 68 } 69 70 void Finish() { 71 ASSERT_FALSE(complete_immediately_); 72 delegate()->OnURLFetchComplete(this); 73 } 74 75 private: 76 int max_failure_count_; 77 int current_failure_count_; 78 bool complete_immediately_; 79 DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcher); 80 }; 81 82 class MockOAuthFetcherFactory : public net::URLFetcherFactory, 83 public net::ScopedURLFetcherFactory { 84 public: 85 MockOAuthFetcherFactory() 86 : net::ScopedURLFetcherFactory(this), 87 response_code_(net::HTTP_OK), 88 complete_immediately_(true) { 89 } 90 virtual ~MockOAuthFetcherFactory() {} 91 virtual net::URLFetcher* CreateURLFetcher( 92 int id, 93 const GURL& url, 94 net::URLFetcher::RequestType request_type, 95 net::URLFetcherDelegate* d) OVERRIDE { 96 url_fetcher_ = new MockOAuthFetcher( 97 response_code_, 98 max_failure_count_, 99 complete_immediately_, 100 url, 101 results_, 102 request_type, 103 d); 104 return url_fetcher_; 105 } 106 void set_response_code(int response_code) { 107 response_code_ = response_code; 108 } 109 void set_max_failure_count(int count) { 110 max_failure_count_ = count; 111 } 112 void set_results(const std::string& results) { 113 results_ = results; 114 } 115 MockOAuthFetcher* get_url_fetcher() { 116 return url_fetcher_; 117 } 118 void set_complete_immediately(bool complete_immediately) { 119 complete_immediately_ = complete_immediately; 120 } 121 private: 122 MockOAuthFetcher* url_fetcher_; 123 int response_code_; 124 bool complete_immediately_; 125 int max_failure_count_; 126 std::string results_; 127 DISALLOW_COPY_AND_ASSIGN(MockOAuthFetcherFactory); 128 }; 129 130 const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg"; 131 const std::string kTestRefreshToken = 132 "1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ"; 133 const std::string kTestUserEmail = "a_user (at) gmail.com"; 134 const std::string kTestUserId = "8675309"; 135 const int kTestExpiresIn = 3920; 136 137 const std::string kDummyGetTokensResult = 138 "{\"access_token\":\"" + kTestAccessToken + "\"," 139 "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "," 140 "\"refresh_token\":\"" + kTestRefreshToken + "\"}"; 141 142 const std::string kDummyRefreshTokenResult = 143 "{\"access_token\":\"" + kTestAccessToken + "\"," 144 "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}"; 145 146 const std::string kDummyUserInfoResult = 147 "{\"email\":\"" + kTestUserEmail + "\"}"; 148 149 const std::string kDummyUserIdResult = 150 "{\"id\":\"" + kTestUserId + "\"}"; 151 152 const std::string kDummyFullUserInfoResult = 153 "{" 154 "\"family_name\": \"Bar\", " 155 "\"name\": \"Foo Bar\", " 156 "\"picture\": \"https://lh4.googleusercontent.com/hash/photo.jpg\", " 157 "\"locale\": \"en\", " 158 "\"gender\": \"male\", " 159 "\"link\": \"https://plus.google.com/+FooBar\", " 160 "\"given_name\": \"Foo\", " 161 "\"id\": \"12345678901234567890\"" 162 "}"; 163 164 const std::string kDummyTokenInfoResult = 165 "{\"issued_to\": \"1234567890.apps.googleusercontent.com\"," 166 "\"audience\": \"1234567890.apps.googleusercontent.com\"," 167 "\"scope\": \"https://googleapis.com/oauth2/v2/tokeninfo\"," 168 "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}"; 169 } 170 171 namespace gaia { 172 173 class GaiaOAuthClientTest : public testing::Test { 174 protected: 175 virtual void SetUp() OVERRIDE { 176 client_info_.client_id = "test_client_id"; 177 client_info_.client_secret = "test_client_secret"; 178 client_info_.redirect_uri = "test_redirect_uri"; 179 }; 180 181 protected: 182 net::TestURLRequestContextGetter* GetRequestContext() { 183 if (!request_context_getter_.get()) { 184 request_context_getter_ = new net::TestURLRequestContextGetter( 185 message_loop_.message_loop_proxy()); 186 } 187 return request_context_getter_.get(); 188 } 189 190 base::MessageLoop message_loop_; 191 scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; 192 OAuthClientInfo client_info_; 193 }; 194 195 class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate { 196 public: 197 MockGaiaOAuthClientDelegate() {} 198 ~MockGaiaOAuthClientDelegate() {} 199 200 MOCK_METHOD3(OnGetTokensResponse, void(const std::string& refresh_token, 201 const std::string& access_token, 202 int expires_in_seconds)); 203 MOCK_METHOD2(OnRefreshTokenResponse, void(const std::string& access_token, 204 int expires_in_seconds)); 205 MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email)); 206 MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id)); 207 MOCK_METHOD0(OnOAuthError, void()); 208 MOCK_METHOD1(OnNetworkError, void(int response_code)); 209 210 // gMock doesn't like methods that take or return scoped_ptr. A 211 // work-around is to create a mock method that takes a raw ptr, and 212 // override the problematic method to call through to it. 213 // https://groups.google.com/a/chromium.org/d/msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ 214 MOCK_METHOD1(OnGetUserInfoResponsePtr, 215 void(const base::DictionaryValue* user_info)); 216 virtual void OnGetUserInfoResponse( 217 scoped_ptr<base::DictionaryValue> user_info) OVERRIDE { 218 user_info_.reset(user_info.release()); 219 OnGetUserInfoResponsePtr(user_info_.get()); 220 } 221 MOCK_METHOD1(OnGetTokenInfoResponsePtr, 222 void(const base::DictionaryValue* token_info)); 223 virtual void OnGetTokenInfoResponse( 224 scoped_ptr<base::DictionaryValue> token_info) OVERRIDE { 225 token_info_.reset(token_info.release()); 226 OnGetTokenInfoResponsePtr(token_info_.get()); 227 } 228 229 private: 230 scoped_ptr<base::DictionaryValue> user_info_; 231 scoped_ptr<base::DictionaryValue> token_info_; 232 DISALLOW_COPY_AND_ASSIGN(MockGaiaOAuthClientDelegate); 233 }; 234 235 TEST_F(GaiaOAuthClientTest, NetworkFailure) { 236 int response_code = net::HTTP_INTERNAL_SERVER_ERROR; 237 238 MockGaiaOAuthClientDelegate delegate; 239 EXPECT_CALL(delegate, OnNetworkError(response_code)) 240 .Times(1); 241 242 MockOAuthFetcherFactory factory; 243 factory.set_response_code(response_code); 244 factory.set_max_failure_count(4); 245 246 GaiaOAuthClient auth(GetRequestContext()); 247 auth.GetTokensFromAuthCode(client_info_, "auth_code", 2, &delegate); 248 } 249 250 TEST_F(GaiaOAuthClientTest, NetworkFailureRecover) { 251 int response_code = net::HTTP_INTERNAL_SERVER_ERROR; 252 253 MockGaiaOAuthClientDelegate delegate; 254 EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken, 255 kTestExpiresIn)).Times(1); 256 257 MockOAuthFetcherFactory factory; 258 factory.set_response_code(response_code); 259 factory.set_max_failure_count(4); 260 factory.set_results(kDummyGetTokensResult); 261 262 GaiaOAuthClient auth(GetRequestContext()); 263 auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); 264 } 265 266 TEST_F(GaiaOAuthClientTest, OAuthFailure) { 267 int response_code = net::HTTP_BAD_REQUEST; 268 269 MockGaiaOAuthClientDelegate delegate; 270 EXPECT_CALL(delegate, OnOAuthError()).Times(1); 271 272 MockOAuthFetcherFactory factory; 273 factory.set_response_code(response_code); 274 factory.set_max_failure_count(-1); 275 factory.set_results(kDummyGetTokensResult); 276 277 GaiaOAuthClient auth(GetRequestContext()); 278 auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); 279 } 280 281 282 TEST_F(GaiaOAuthClientTest, GetTokensSuccess) { 283 MockGaiaOAuthClientDelegate delegate; 284 EXPECT_CALL(delegate, OnGetTokensResponse(kTestRefreshToken, kTestAccessToken, 285 kTestExpiresIn)).Times(1); 286 287 MockOAuthFetcherFactory factory; 288 factory.set_results(kDummyGetTokensResult); 289 290 GaiaOAuthClient auth(GetRequestContext()); 291 auth.GetTokensFromAuthCode(client_info_, "auth_code", -1, &delegate); 292 } 293 294 TEST_F(GaiaOAuthClientTest, RefreshTokenSuccess) { 295 MockGaiaOAuthClientDelegate delegate; 296 EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken, 297 kTestExpiresIn)).Times(1); 298 299 MockOAuthFetcherFactory factory; 300 factory.set_results(kDummyRefreshTokenResult); 301 factory.set_complete_immediately(false); 302 303 GaiaOAuthClient auth(GetRequestContext()); 304 auth.RefreshToken(client_info_, "refresh_token", std::vector<std::string>(), 305 -1, &delegate); 306 EXPECT_THAT(factory.get_url_fetcher()->upload_data(), 307 Not(HasSubstr("scope"))); 308 factory.get_url_fetcher()->Finish(); 309 } 310 311 TEST_F(GaiaOAuthClientTest, RefreshTokenDownscopingSuccess) { 312 MockGaiaOAuthClientDelegate delegate; 313 EXPECT_CALL(delegate, OnRefreshTokenResponse(kTestAccessToken, 314 kTestExpiresIn)).Times(1); 315 316 MockOAuthFetcherFactory factory; 317 factory.set_results(kDummyRefreshTokenResult); 318 factory.set_complete_immediately(false); 319 320 GaiaOAuthClient auth(GetRequestContext()); 321 auth.RefreshToken(client_info_, "refresh_token", 322 std::vector<std::string>(1, "scope4test"), -1, &delegate); 323 EXPECT_THAT(factory.get_url_fetcher()->upload_data(), 324 HasSubstr("&scope=scope4test")); 325 factory.get_url_fetcher()->Finish(); 326 } 327 328 329 TEST_F(GaiaOAuthClientTest, GetUserEmail) { 330 MockGaiaOAuthClientDelegate delegate; 331 EXPECT_CALL(delegate, OnGetUserEmailResponse(kTestUserEmail)).Times(1); 332 333 MockOAuthFetcherFactory factory; 334 factory.set_results(kDummyUserInfoResult); 335 336 GaiaOAuthClient auth(GetRequestContext()); 337 auth.GetUserEmail("access_token", 1, &delegate); 338 } 339 340 TEST_F(GaiaOAuthClientTest, GetUserId) { 341 MockGaiaOAuthClientDelegate delegate; 342 EXPECT_CALL(delegate, OnGetUserIdResponse(kTestUserId)).Times(1); 343 344 MockOAuthFetcherFactory factory; 345 factory.set_results(kDummyUserIdResult); 346 347 GaiaOAuthClient auth(GetRequestContext()); 348 auth.GetUserId("access_token", 1, &delegate); 349 } 350 351 TEST_F(GaiaOAuthClientTest, GetUserInfo) { 352 const base::DictionaryValue* captured_result; 353 354 MockGaiaOAuthClientDelegate delegate; 355 EXPECT_CALL(delegate, OnGetUserInfoResponsePtr(_)) 356 .WillOnce(SaveArg<0>(&captured_result)); 357 358 MockOAuthFetcherFactory factory; 359 factory.set_results(kDummyFullUserInfoResult); 360 361 GaiaOAuthClient auth(GetRequestContext()); 362 auth.GetUserInfo("access_token", 1, &delegate); 363 364 scoped_ptr<base::Value> value( 365 base::JSONReader::Read(kDummyFullUserInfoResult)); 366 DCHECK(value); 367 ASSERT_TRUE(value->IsType(base::Value::TYPE_DICTIONARY)); 368 base::DictionaryValue* expected_result; 369 value->GetAsDictionary(&expected_result); 370 371 ASSERT_TRUE(expected_result->Equals(captured_result)); 372 } 373 374 TEST_F(GaiaOAuthClientTest, GetTokenInfo) { 375 const base::DictionaryValue* captured_result; 376 377 MockGaiaOAuthClientDelegate delegate; 378 EXPECT_CALL(delegate, OnGetTokenInfoResponsePtr(_)) 379 .WillOnce(SaveArg<0>(&captured_result)); 380 381 MockOAuthFetcherFactory factory; 382 factory.set_results(kDummyTokenInfoResult); 383 384 GaiaOAuthClient auth(GetRequestContext()); 385 auth.GetTokenInfo("access_token", 1, &delegate); 386 387 std::string issued_to; 388 ASSERT_TRUE(captured_result->GetString("issued_to", &issued_to)); 389 ASSERT_EQ("1234567890.apps.googleusercontent.com", issued_to); 390 } 391 392 } // namespace gaia 393