summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/chrome_common.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/net/gaia/oauth2_api_call_flow.cc152
-rw-r--r--chrome/common/net/gaia/oauth2_api_call_flow.h126
-rw-r--r--chrome/common/net/gaia/oauth2_api_call_flow_unittest.cc263
5 files changed, 544 insertions, 0 deletions
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index c99d408..f9b6663 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -409,6 +409,8 @@
'common/net/gaia/oauth2_access_token_consumer.h',
'common/net/gaia/oauth2_access_token_fetcher.cc',
'common/net/gaia/oauth2_access_token_fetcher.h',
+ 'common/net/gaia/oauth2_api_call_flow.cc',
+ 'common/net/gaia/oauth2_api_call_flow.h',
'common/net/gaia/oauth2_mint_token_consumer.h',
'common/net/gaia/oauth2_mint_token_fetcher.cc',
'common/net/gaia/oauth2_mint_token_fetcher.h',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 997ae3f..e180a03 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2078,6 +2078,7 @@
'common/net/gaia/google_service_auth_error_unittest.cc',
'common/net/gaia/oauth_request_signer_unittest.cc',
'common/net/gaia/oauth2_access_token_fetcher_unittest.cc',
+ 'common/net/gaia/oauth2_api_call_flow_unittest.cc',
'common/net/gaia/oauth2_mint_token_fetcher_unittest.cc',
'common/net/gaia/oauth2_mint_token_flow_unittest.cc',
'common/net/gaia/oauth2_revocation_fetcher_unittest.cc',
diff --git a/chrome/common/net/gaia/oauth2_api_call_flow.cc b/chrome/common/net/gaia/oauth2_api_call_flow.cc
new file mode 100644
index 0000000..9f6330d
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_api_call_flow.cc
@@ -0,0 +1,152 @@
+// 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.
+
+#include "chrome/common/net/gaia/oauth2_api_call_flow.h"
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/common/net/gaia/gaia_urls.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using content::URLFetcher;
+using content::URLFetcherDelegate;
+using net::ResponseCookies;
+using net::URLRequestContextGetter;
+using net::URLRequestStatus;
+
+OAuth2ApiCallFlow::OAuth2ApiCallFlow(
+ net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes)
+ : context_(context),
+ refresh_token_(refresh_token),
+ access_token_(access_token),
+ scopes_(scopes),
+ state_(INITIAL),
+ tried_mint_access_token_(false) {
+}
+
+OAuth2ApiCallFlow::~OAuth2ApiCallFlow() { }
+
+void OAuth2ApiCallFlow::Start() {
+ BeginApiCall();
+}
+
+void OAuth2ApiCallFlow::BeginApiCall() {
+ CHECK(state_ == INITIAL || state_ == MINT_ACCESS_TOKEN_DONE);
+
+ // If the access token is empty then directly try to mint one.
+ if (access_token_.empty()) {
+ BeginMintAccessToken();
+ return;
+ } else {
+ state_ = API_CALL_STARTED;
+
+ url_fetcher_.reset(CreateURLFetcher());
+ url_fetcher_->Start(); // OnURLFetchComplete will be called.
+ }
+}
+
+void OAuth2ApiCallFlow::EndApiCall(const URLFetcher* source) {
+ CHECK_EQ(API_CALL_STARTED, state_);
+ state_ = API_CALL_DONE;
+
+ URLRequestStatus status = source->GetStatus();
+ if (!status.is_success()) {
+ ProcessApiCallFailure(source);
+ return;
+ }
+
+ // If the response code is 401 Unauthorized then access token may have
+ // expired. So try generating a new access token.
+ if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
+ // If we already tried minting a new access token, don't do it again.
+ if (tried_mint_access_token_)
+ ProcessApiCallFailure(source);
+ else
+ BeginMintAccessToken();
+
+ return;
+ }
+
+ if (source->GetResponseCode() != net::HTTP_OK) {
+ ProcessApiCallFailure(source);
+ return;
+ }
+
+ ProcessApiCallSuccess(source);
+}
+
+void OAuth2ApiCallFlow::BeginMintAccessToken() {
+ CHECK(state_ == INITIAL || state_ == API_CALL_DONE);
+ CHECK(!tried_mint_access_token_);
+ state_ = MINT_ACCESS_TOKEN_STARTED;
+ tried_mint_access_token_ = true;
+
+ oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher());
+ oauth2_access_token_fetcher_->Start(
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ refresh_token_,
+ scopes_);
+}
+
+void OAuth2ApiCallFlow::EndMintAccessToken(
+ const GoogleServiceAuthError* error) {
+ CHECK_EQ(MINT_ACCESS_TOKEN_STARTED, state_);
+
+ if (!error) {
+ state_ = MINT_ACCESS_TOKEN_DONE;
+ BeginApiCall();
+ } else {
+ state_ = ERROR_STATE;
+ ProcessMintAccessTokenFailure(*error);
+ }
+}
+
+OAuth2AccessTokenFetcher* OAuth2ApiCallFlow::CreateAccessTokenFetcher() {
+ return new OAuth2AccessTokenFetcher(this, context_);
+}
+
+void OAuth2ApiCallFlow::OnURLFetchComplete(const URLFetcher* source) {
+ CHECK(source);
+ CHECK_EQ(API_CALL_STARTED, state_);
+ EndApiCall(source);
+}
+
+void OAuth2ApiCallFlow::OnGetTokenSuccess(const std::string& access_token) {
+ access_token_ = access_token;
+ EndMintAccessToken(NULL);
+}
+
+void OAuth2ApiCallFlow::OnGetTokenFailure(
+ const GoogleServiceAuthError& error) {
+ EndMintAccessToken(&error);
+}
+
+URLFetcher* OAuth2ApiCallFlow::CreateURLFetcher() {
+ std::string body = CreateApiCallBody();
+ bool empty_body = body.empty();
+ URLFetcher* result = URLFetcher::Create(
+ 0,
+ CreateApiCallUrl(),
+ empty_body ? URLFetcher::GET : URLFetcher::POST,
+ this);
+
+ result->SetRequestContext(context_);
+ result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+
+ if (!empty_body)
+ result->SetUploadData("application/x-www-form-urlencoded", body);
+
+ return result;
+}
diff --git a/chrome/common/net/gaia/oauth2_api_call_flow.h b/chrome/common/net/gaia/oauth2_api_call_flow.h
new file mode 100644
index 0000000..639b45f
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_api_call_flow.h
@@ -0,0 +1,126 @@
+// 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.
+
+#ifndef CHROME_COMMON_NET_GAIA_OAUTH2_API_CALL_FLOW_H_
+#define CHROME_COMMON_NET_GAIA_OAUTH2_API_CALL_FLOW_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/net/gaia/oauth2_access_token_consumer.h"
+#include "chrome/common/net/gaia/oauth2_access_token_fetcher.h"
+#include "chrome/common/net/gaia/oauth2_mint_token_consumer.h"
+#include "chrome/common/net/gaia/oauth2_mint_token_fetcher.h"
+#include "content/public/common/url_fetcher.h"
+#include "content/public/common/url_fetcher_delegate.h"
+
+class GoogleServiceAuthError;
+class OAuth2MintTokenFlowTest;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// Base calss for all classes that implement a flow to call OAuth2
+// enabled APIs.
+//
+// Given a refresh token, an access token, and a list of scopes an OAuth2
+// enabled API is called in the following way:
+// 1. Try the given access token to call the API.
+// 2. If that does not work, use the refresh token and scopes to generate
+// a new access token.
+// 3. Try the new access token to call the API.
+//
+// This class abstracts the basic steps and exposes template methods
+// for sub-classes to implement for API specific details.
+class OAuth2ApiCallFlow
+ : public content::URLFetcherDelegate,
+ public OAuth2AccessTokenConsumer {
+ public:
+ // Creates an instance that works with the given data.
+ // Note that access_token can be empty. In that case, the flow will skip
+ // the first step (of trying an existing acces token).
+ OAuth2ApiCallFlow(
+ net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes);
+
+ virtual ~OAuth2ApiCallFlow();
+
+ // Start the flow.
+ void Start();
+
+ // OAuth2AccessTokenFetcher implementation.
+ virtual void OnGetTokenSuccess(const std::string& access_token) OVERRIDE;
+ virtual void OnGetTokenFailure(const GoogleServiceAuthError& error) OVERRIDE;
+
+ // content::URLFetcherDelegate implementation.
+ virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE;
+
+ protected:
+ // Template methods for sub-classes.
+
+ // Methods to help create HTTP request.
+ virtual GURL CreateApiCallUrl() = 0;
+ virtual std::string CreateApiCallBody() = 0;
+
+ // Sub-classes can expose appropriate observer interface by implementing
+ // these template methods.
+ // Called when the API call finished successfully.
+ virtual void ProcessApiCallSuccess(const content::URLFetcher* source) = 0;
+ // Called when the API call failed.
+ virtual void ProcessApiCallFailure(const content::URLFetcher* source) = 0;
+ // Called when a new access token is generated.
+ virtual void ProcessNewAccessToken(const std::string& access_token) = 0;
+ virtual void ProcessMintAccessTokenFailure(
+ const GoogleServiceAuthError& error) = 0;
+
+ private:
+ // The steps this class performs are:
+ // 1. Try existing access token.
+ // 2. If that works, flow is done. If not, generate a new access token.
+ // 3. Try using new access token.
+ enum State {
+ INITIAL,
+ API_CALL_STARTED,
+ API_CALL_DONE,
+ MINT_ACCESS_TOKEN_STARTED,
+ MINT_ACCESS_TOKEN_DONE,
+ ERROR_STATE
+ };
+
+ friend class OAuth2ApiCallFlowTest;
+
+ // Helper to create an instnace of access token fetcher.
+ // Caller owns the returned instance.
+ virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher();
+
+ // Creates an instance of URLFetcher that does not send or save cookies.
+ // The URLFether's method will be GET if body is empty, POST otherwise.
+ // Caller owns the returned instance.
+ virtual content::URLFetcher* CreateURLFetcher();
+
+ // Helper methods to implement the state machien for the flow.
+ void BeginApiCall();
+ void EndApiCall(const content::URLFetcher* source);
+ void BeginMintAccessToken();
+ void EndMintAccessToken(const GoogleServiceAuthError* error);
+
+ net::URLRequestContextGetter* context_;
+ std::string refresh_token_;
+ std::string access_token_;
+ std::vector<std::string> scopes_;
+
+ State state_;
+ // Whether we have already tried minting access token once.
+ bool tried_mint_access_token_;
+
+ scoped_ptr<content::URLFetcher> url_fetcher_;
+ scoped_ptr<OAuth2AccessTokenFetcher> oauth2_access_token_fetcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2ApiCallFlow);
+};
+
+#endif // CHROME_COMMON_NET_GAIA_OAUTH2_API_CALL_FLOW_H_
diff --git a/chrome/common/net/gaia/oauth2_api_call_flow_unittest.cc b/chrome/common/net/gaia/oauth2_api_call_flow_unittest.cc
new file mode 100644
index 0000000..b72335f
--- /dev/null
+++ b/chrome/common/net/gaia/oauth2_api_call_flow_unittest.cc
@@ -0,0 +1,263 @@
+// 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 OAuth2MintTokenFlow.
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/common/net/gaia/gaia_urls.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "chrome/common/net/gaia/oauth2_access_token_consumer.h"
+#include "chrome/common/net/gaia/oauth2_access_token_fetcher.h"
+#include "chrome/common/net/gaia/oauth2_api_call_flow.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/common/url_fetcher.h"
+#include "content/public/common/url_fetcher_delegate.h"
+#include "content/public/common/url_fetcher_factory.h"
+#include "content/test/test_url_fetcher_factory.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using content::URLFetcher;
+using content::URLFetcherDelegate;
+using content::URLFetcherFactory;
+using net::URLRequestStatus;
+using testing::_;
+using testing::Return;
+
+namespace {
+static GURL CreateApiUrl() {
+ return GURL("https://www.googleapis.com/someapi");
+}
+
+static std::vector<std::string> CreateTestScopes() {
+ std::vector<std::string> scopes;
+ scopes.push_back("scope1");
+ scopes.push_back("scope2");
+ return scopes;
+}
+}
+
+class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
+ public URLFetcherFactory {
+public:
+ MockUrlFetcherFactory()
+ : ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+ }
+ virtual ~MockUrlFetcherFactory() {}
+
+ MOCK_METHOD4(
+ CreateURLFetcher,
+ URLFetcher* (int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d));
+};
+
+class MockAccessTokenFetcher : public OAuth2AccessTokenFetcher {
+ public:
+ MockAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
+ net::URLRequestContextGetter* getter)
+ : OAuth2AccessTokenFetcher(consumer, getter) {}
+ ~MockAccessTokenFetcher() {}
+
+ MOCK_METHOD4(Start,
+ void (const std::string& client_id,
+ const std::string& client_secret,
+ const std::string& refresh_token,
+ const std::vector<std::string>& scopes));
+};
+
+class MockApiCallFlow : public OAuth2ApiCallFlow {
+ public:
+ MockApiCallFlow(net::URLRequestContextGetter* context,
+ const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes)
+ : OAuth2ApiCallFlow(context, refresh_token, access_token, scopes) {}
+ ~MockApiCallFlow() {}
+
+ MOCK_METHOD0(CreateApiCallUrl, GURL ());
+ MOCK_METHOD0(CreateApiCallBody, std::string ());
+ MOCK_METHOD1(ProcessApiCallSuccess,
+ void (const content::URLFetcher* source));
+ MOCK_METHOD1(ProcessApiCallFailure,
+ void (const content::URLFetcher* source));
+ MOCK_METHOD1(ProcessNewAccessToken,
+ void (const std::string& access_token));
+ MOCK_METHOD1(ProcessMintAccessTokenFailure,
+ void (const GoogleServiceAuthError& error));
+ MOCK_METHOD0(CreateAccessTokenFetcher, OAuth2AccessTokenFetcher* ());
+ // MOCK_METHOD0(CreateURLFetcher, URLFetcher* ());
+};
+
+class OAuth2ApiCallFlowTest : public testing::Test {
+ public:
+ OAuth2ApiCallFlowTest() {}
+ virtual ~OAuth2ApiCallFlowTest() {}
+
+ protected:
+ void SetupAccessTokenFetcher(
+ const std::string& rt, const std::vector<std::string>& scopes) {
+ EXPECT_CALL(*access_token_fetcher_,
+ Start(GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ rt, scopes))
+ .Times(1);
+ EXPECT_CALL(*flow_, CreateAccessTokenFetcher())
+ .WillOnce(Return(access_token_fetcher_.release()));
+ }
+
+ TestURLFetcher* CreateURLFetcher(
+ const GURL& url, bool fetch_succeeds,
+ int response_code, const std::string& body) {
+ TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, flow_.get());
+ URLRequestStatus::Status status =
+ fetch_succeeds ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED;
+ url_fetcher->set_status(URLRequestStatus(status, 0));
+
+ if (response_code != 0)
+ url_fetcher->set_response_code(response_code);
+
+ if (!body.empty())
+ url_fetcher->SetResponseString(body);
+
+ return url_fetcher;
+ }
+
+ void CreateFlow(const std::string& refresh_token,
+ const std::string& access_token,
+ const std::vector<std::string>& scopes) {
+ flow_.reset(new MockApiCallFlow(
+ profile_.GetRequestContext(),
+ refresh_token,
+ access_token,
+ scopes));
+ access_token_fetcher_.reset(new MockAccessTokenFetcher(
+ flow_.get(), profile_.GetRequestContext()));
+ }
+
+ TestURLFetcher* SetupApiCall(bool succeeds, net::HttpStatusCode status) {
+ GURL url(CreateApiUrl());
+ EXPECT_CALL(*flow_, CreateApiCallBody()).WillOnce(Return(""));
+ EXPECT_CALL(*flow_, CreateApiCallUrl()).WillOnce(Return(url));
+ TestURLFetcher* url_fetcher = CreateURLFetcher(
+ url, succeeds, status, "");
+ EXPECT_CALL(factory_, CreateURLFetcher(_, url, _, _))
+ .WillOnce(Return(url_fetcher));
+ return url_fetcher;
+ }
+
+ MockUrlFetcherFactory factory_;
+ scoped_ptr<MockApiCallFlow> flow_;
+ scoped_ptr<MockAccessTokenFetcher> access_token_fetcher_;
+ TestingProfile profile_;
+};
+
+TEST_F(OAuth2ApiCallFlowTest, FirstApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher));
+ flow_->Start();
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, SecondApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher1);
+ TestURLFetcher* url_fetcher2 = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher2));
+ flow_->OnGetTokenSuccess(at);
+ flow_->OnURLFetchComplete(url_fetcher2);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, SecondApiCallFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher1 = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher1);
+ TestURLFetcher* url_fetcher2 = SetupApiCall(false, net::HTTP_UNAUTHORIZED);
+ EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher2));
+ flow_->OnGetTokenSuccess(at);
+ flow_->OnURLFetchComplete(url_fetcher2);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, NewTokenGenerationFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, at, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_UNAUTHORIZED);
+ flow_->Start();
+ SetupAccessTokenFetcher(rt, scopes);
+ flow_->OnURLFetchComplete(url_fetcher);
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ EXPECT_CALL(*flow_, ProcessMintAccessTokenFailure(error));
+ flow_->OnGetTokenFailure(error);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenFirstApiCallSucceeds) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, "", scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(true, net::HTTP_OK);
+ EXPECT_CALL(*flow_, ProcessApiCallSuccess(url_fetcher));
+ flow_->Start();
+ flow_->OnGetTokenSuccess(at);
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenApiCallFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, "", scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ TestURLFetcher* url_fetcher = SetupApiCall(false, net::HTTP_BAD_GATEWAY);
+ EXPECT_CALL(*flow_, ProcessApiCallFailure(url_fetcher));
+ flow_->Start();
+ flow_->OnGetTokenSuccess(at);
+ flow_->OnURLFetchComplete(url_fetcher);
+}
+
+TEST_F(OAuth2ApiCallFlowTest, EmptyAccessTokenNewTokenGenerationFails) {
+ std::string rt = "refresh_token";
+ std::string at = "access_token";
+ std::vector<std::string> scopes(CreateTestScopes());
+
+ CreateFlow(rt, "", scopes);
+ SetupAccessTokenFetcher(rt, scopes);
+ GoogleServiceAuthError error(
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
+ EXPECT_CALL(*flow_, ProcessMintAccessTokenFailure(error));
+ flow_->Start();
+ flow_->OnGetTokenFailure(error);
+}