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 OAuth2MintTokenFlow. 6 7 #include <string> 8 #include <vector> 9 10 #include "base/json/json_reader.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "base/strings/utf_string_conversions.h" 13 #include "base/values.h" 14 #include "google_apis/gaia/google_service_auth_error.h" 15 #include "google_apis/gaia/oauth2_mint_token_flow.h" 16 #include "net/url_request/test_url_fetcher_factory.h" 17 #include "net/url_request/url_request_status.h" 18 #include "testing/gmock/include/gmock/gmock.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 21 using net::TestURLFetcher; 22 using net::URLFetcher; 23 using net::URLRequestStatus; 24 using testing::_; 25 using testing::StrictMock; 26 27 namespace { 28 29 static const char kValidTokenResponse[] = 30 "{" 31 " \"token\": \"at1\"," 32 " \"issueAdvice\": \"Auto\"," 33 " \"expiresIn\": \"3600\"" 34 "}"; 35 static const char kTokenResponseNoAccessToken[] = 36 "{" 37 " \"issueAdvice\": \"Auto\"" 38 "}"; 39 40 static const char kValidIssueAdviceResponse[] = 41 "{" 42 " \"issueAdvice\": \"consent\"," 43 " \"consent\": {" 44 " \"oauthClient\": {" 45 " \"name\": \"Test app\"," 46 " \"iconUri\": \"\"," 47 " \"developerEmail\": \"munjal (at) chromium.org\"" 48 " }," 49 " \"scopes\": [" 50 " {" 51 " \"description\": \"Manage your calendars\"," 52 " \"detail\": \"\nView and manage your calendars\n\"" 53 " }," 54 " {" 55 " \"description\": \"Manage your documents\"," 56 " \"detail\": \"\nView your documents\nUpload new documents\n\"" 57 " }" 58 " ]" 59 " }" 60 "}"; 61 62 static const char kIssueAdviceResponseNoDescription[] = 63 "{" 64 " \"issueAdvice\": \"consent\"," 65 " \"consent\": {" 66 " \"oauthClient\": {" 67 " \"name\": \"Test app\"," 68 " \"iconUri\": \"\"," 69 " \"developerEmail\": \"munjal (at) chromium.org\"" 70 " }," 71 " \"scopes\": [" 72 " {" 73 " \"description\": \"Manage your calendars\"," 74 " \"detail\": \"\nView and manage your calendars\n\"" 75 " }," 76 " {" 77 " \"detail\": \"\nView your documents\nUpload new documents\n\"" 78 " }" 79 " ]" 80 " }" 81 "}"; 82 83 static const char kIssueAdviceResponseNoDetail[] = 84 "{" 85 " \"issueAdvice\": \"consent\"," 86 " \"consent\": {" 87 " \"oauthClient\": {" 88 " \"name\": \"Test app\"," 89 " \"iconUri\": \"\"," 90 " \"developerEmail\": \"munjal (at) chromium.org\"" 91 " }," 92 " \"scopes\": [" 93 " {" 94 " \"description\": \"Manage your calendars\"," 95 " \"detail\": \"\nView and manage your calendars\n\"" 96 " }," 97 " {" 98 " \"description\": \"Manage your documents\"" 99 " }" 100 " ]" 101 " }" 102 "}"; 103 104 std::vector<std::string> CreateTestScopes() { 105 std::vector<std::string> scopes; 106 scopes.push_back("http://scope1"); 107 scopes.push_back("http://scope2"); 108 return scopes; 109 } 110 111 static IssueAdviceInfo CreateIssueAdvice() { 112 IssueAdviceInfo ia; 113 IssueAdviceInfoEntry e1; 114 e1.description = ASCIIToUTF16("Manage your calendars"); 115 e1.details.push_back(ASCIIToUTF16("View and manage your calendars")); 116 ia.push_back(e1); 117 IssueAdviceInfoEntry e2; 118 e2.description = ASCIIToUTF16("Manage your documents"); 119 e2.details.push_back(ASCIIToUTF16("View your documents")); 120 e2.details.push_back(ASCIIToUTF16("Upload new documents")); 121 ia.push_back(e2); 122 return ia; 123 } 124 125 class MockDelegate : public OAuth2MintTokenFlow::Delegate { 126 public: 127 MockDelegate() {} 128 ~MockDelegate() {} 129 130 MOCK_METHOD2(OnMintTokenSuccess, void(const std::string& access_token, 131 int time_to_live)); 132 MOCK_METHOD1(OnIssueAdviceSuccess, 133 void (const IssueAdviceInfo& issue_advice)); 134 MOCK_METHOD1(OnMintTokenFailure, 135 void(const GoogleServiceAuthError& error)); 136 }; 137 138 class MockMintTokenFlow : public OAuth2MintTokenFlow { 139 public: 140 explicit MockMintTokenFlow(MockDelegate* delegate, 141 const OAuth2MintTokenFlow::Parameters& parameters ) 142 : OAuth2MintTokenFlow(NULL, delegate, parameters) {} 143 ~MockMintTokenFlow() {} 144 145 MOCK_METHOD0(CreateAccessTokenFetcher, OAuth2AccessTokenFetcher*()); 146 }; 147 148 } // namespace 149 150 class OAuth2MintTokenFlowTest : public testing::Test { 151 public: 152 OAuth2MintTokenFlowTest() {} 153 virtual ~OAuth2MintTokenFlowTest() { } 154 155 protected: 156 void CreateFlow(OAuth2MintTokenFlow::Mode mode) { 157 return CreateFlow(&delegate_, mode); 158 } 159 160 void CreateFlow(MockDelegate* delegate, 161 OAuth2MintTokenFlow::Mode mode) { 162 std::string rt = "refresh_token"; 163 std::string ext_id = "ext1"; 164 std::string client_id = "client1"; 165 std::vector<std::string> scopes(CreateTestScopes()); 166 flow_.reset(new MockMintTokenFlow( 167 delegate, 168 OAuth2MintTokenFlow::Parameters(rt, ext_id, client_id, scopes, mode))); 169 } 170 171 // Helper to parse the given string to DictionaryValue. 172 static base::DictionaryValue* ParseJson(const std::string& str) { 173 scoped_ptr<Value> value(base::JSONReader::Read(str)); 174 EXPECT_TRUE(value.get()); 175 EXPECT_EQ(Value::TYPE_DICTIONARY, value->GetType()); 176 return static_cast<base::DictionaryValue*>(value.release()); 177 } 178 179 scoped_ptr<MockMintTokenFlow> flow_; 180 StrictMock<MockDelegate> delegate_; 181 }; 182 183 TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) { 184 { // Issue advice mode. 185 CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE); 186 std::string body = flow_->CreateApiCallBody(); 187 std::string expected_body( 188 "force=false" 189 "&response_type=none" 190 "&scope=http://scope1+http://scope2" 191 "&client_id=client1" 192 "&origin=ext1"); 193 EXPECT_EQ(expected_body, body); 194 } 195 { // Record grant mode. 196 CreateFlow(OAuth2MintTokenFlow::MODE_RECORD_GRANT); 197 std::string body = flow_->CreateApiCallBody(); 198 std::string expected_body( 199 "force=true" 200 "&response_type=none" 201 "&scope=http://scope1+http://scope2" 202 "&client_id=client1" 203 "&origin=ext1"); 204 EXPECT_EQ(expected_body, body); 205 } 206 { // Mint token no force mode. 207 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 208 std::string body = flow_->CreateApiCallBody(); 209 std::string expected_body( 210 "force=false" 211 "&response_type=token" 212 "&scope=http://scope1+http://scope2" 213 "&client_id=client1" 214 "&origin=ext1"); 215 EXPECT_EQ(expected_body, body); 216 } 217 { // Mint token force mode. 218 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE); 219 std::string body = flow_->CreateApiCallBody(); 220 std::string expected_body( 221 "force=true" 222 "&response_type=token" 223 "&scope=http://scope1+http://scope2" 224 "&client_id=client1" 225 "&origin=ext1"); 226 EXPECT_EQ(expected_body, body); 227 } 228 } 229 230 TEST_F(OAuth2MintTokenFlowTest, ParseMintTokenResponse) { 231 { // Access token missing. 232 scoped_ptr<base::DictionaryValue> json( 233 ParseJson(kTokenResponseNoAccessToken)); 234 std::string at; 235 int ttl; 236 EXPECT_FALSE(OAuth2MintTokenFlow::ParseMintTokenResponse(json.get(), &at, 237 &ttl)); 238 EXPECT_TRUE(at.empty()); 239 } 240 { // All good. 241 scoped_ptr<base::DictionaryValue> json(ParseJson(kValidTokenResponse)); 242 std::string at; 243 int ttl; 244 EXPECT_TRUE(OAuth2MintTokenFlow::ParseMintTokenResponse(json.get(), &at, 245 &ttl)); 246 EXPECT_EQ("at1", at); 247 EXPECT_EQ(3600, ttl); 248 } 249 } 250 251 TEST_F(OAuth2MintTokenFlowTest, ParseIssueAdviceResponse) { 252 { // Description missing. 253 scoped_ptr<base::DictionaryValue> json( 254 ParseJson(kIssueAdviceResponseNoDescription)); 255 IssueAdviceInfo ia; 256 EXPECT_FALSE(OAuth2MintTokenFlow::ParseIssueAdviceResponse( 257 json.get(), &ia)); 258 EXPECT_TRUE(ia.empty()); 259 } 260 { // Detail missing. 261 scoped_ptr<base::DictionaryValue> json( 262 ParseJson(kIssueAdviceResponseNoDetail)); 263 IssueAdviceInfo ia; 264 EXPECT_FALSE(OAuth2MintTokenFlow::ParseIssueAdviceResponse( 265 json.get(), &ia)); 266 EXPECT_TRUE(ia.empty()); 267 } 268 { // All good. 269 scoped_ptr<base::DictionaryValue> json( 270 ParseJson(kValidIssueAdviceResponse)); 271 IssueAdviceInfo ia; 272 EXPECT_TRUE(OAuth2MintTokenFlow::ParseIssueAdviceResponse( 273 json.get(), &ia)); 274 IssueAdviceInfo ia_expected(CreateIssueAdvice()); 275 EXPECT_EQ(ia_expected, ia); 276 } 277 } 278 279 TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallSuccess) { 280 { // No body. 281 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 282 url_fetcher.SetResponseString(std::string()); 283 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 284 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 285 flow_->ProcessApiCallSuccess(&url_fetcher); 286 } 287 { // Bad json. 288 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 289 url_fetcher.SetResponseString("foo"); 290 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 291 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 292 flow_->ProcessApiCallSuccess(&url_fetcher); 293 } 294 { // Valid json: no access token. 295 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 296 url_fetcher.SetResponseString(kTokenResponseNoAccessToken); 297 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 298 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 299 flow_->ProcessApiCallSuccess(&url_fetcher); 300 } 301 { // Valid json: good token response. 302 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 303 url_fetcher.SetResponseString(kValidTokenResponse); 304 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 305 EXPECT_CALL(delegate_, OnMintTokenSuccess("at1", 3600)); 306 flow_->ProcessApiCallSuccess(&url_fetcher); 307 } 308 { // Valid json: no description. 309 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 310 url_fetcher.SetResponseString(kIssueAdviceResponseNoDescription); 311 CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE); 312 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 313 flow_->ProcessApiCallSuccess(&url_fetcher); 314 } 315 { // Valid json: no detail. 316 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 317 url_fetcher.SetResponseString(kIssueAdviceResponseNoDetail); 318 CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE); 319 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 320 flow_->ProcessApiCallSuccess(&url_fetcher); 321 } 322 { // Valid json: good issue advice response. 323 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 324 url_fetcher.SetResponseString(kValidIssueAdviceResponse); 325 CreateFlow(OAuth2MintTokenFlow::MODE_ISSUE_ADVICE); 326 IssueAdviceInfo ia(CreateIssueAdvice()); 327 EXPECT_CALL(delegate_, OnIssueAdviceSuccess(ia)); 328 flow_->ProcessApiCallSuccess(&url_fetcher); 329 } 330 } 331 332 TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallFailure) { 333 { // Null delegate should work fine. 334 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 335 url_fetcher.set_status(URLRequestStatus(URLRequestStatus::FAILED, 101)); 336 CreateFlow(NULL, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 337 flow_->ProcessApiCallFailure(&url_fetcher); 338 } 339 340 { // Non-null delegate. 341 TestURLFetcher url_fetcher(1, GURL("http://www.google.com"), NULL); 342 url_fetcher.set_status(URLRequestStatus(URLRequestStatus::FAILED, 101)); 343 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 344 EXPECT_CALL(delegate_, OnMintTokenFailure(_)); 345 flow_->ProcessApiCallFailure(&url_fetcher); 346 } 347 } 348 349 TEST_F(OAuth2MintTokenFlowTest, ProcessMintAccessTokenFailure) { 350 { // Null delegate should work fine. 351 GoogleServiceAuthError error( 352 GoogleServiceAuthError::FromConnectionError(101)); 353 CreateFlow(NULL, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 354 flow_->ProcessMintAccessTokenFailure(error); 355 } 356 357 { // Non-null delegate. 358 GoogleServiceAuthError error( 359 GoogleServiceAuthError::FromConnectionError(101)); 360 CreateFlow(OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE); 361 EXPECT_CALL(delegate_, OnMintTokenFailure(error)); 362 flow_->ProcessMintAccessTokenFailure(error); 363 } 364 } 365