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
24using ::testing::_;
25using ::testing::Eq;
26using ::testing::HasSubstr;
27using ::testing::Pointee;
28using ::testing::SaveArg;
29
30namespace {
31
32// Responds as though OAuth returned from the server.
33class 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
82class 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
130const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg";
131const std::string kTestRefreshToken =
132    "1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ";
133const std::string kTestUserEmail = "a_user@gmail.com";
134const std::string kTestUserId = "8675309";
135const int kTestExpiresIn = 3920;
136
137const std::string kDummyGetTokensResult =
138    "{\"access_token\":\"" + kTestAccessToken + "\","
139    "\"expires_in\":" + base::IntToString(kTestExpiresIn) + ","
140    "\"refresh_token\":\"" + kTestRefreshToken + "\"}";
141
142const std::string kDummyRefreshTokenResult =
143    "{\"access_token\":\"" + kTestAccessToken + "\","
144    "\"expires_in\":" + base::IntToString(kTestExpiresIn) + "}";
145
146const std::string kDummyUserInfoResult =
147    "{\"email\":\"" + kTestUserEmail + "\"}";
148
149const std::string kDummyUserIdResult =
150    "{\"id\":\"" + kTestUserId + "\"}";
151
152const 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
164const 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
171namespace gaia {
172
173class 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
195class 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
235TEST_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
250TEST_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
266TEST_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
282TEST_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
294TEST_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
311TEST_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
329TEST_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
340TEST_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
351TEST_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
374TEST_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