// Copyright 2013 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 "google_apis/drive/request_sender.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" #include "google_apis/drive/base_requests.h" #include "google_apis/drive/dummy_auth_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace google_apis { namespace { const char kTestRefreshToken[] = "valid-refresh-token"; const char kTestAccessToken[] = "valid-access-token"; // Enum for indicating the reason why a request is finished. enum FinishReason { NONE, SUCCESS, CANCEL, AUTH_FAILURE, }; // AuthService for testing purpose. It accepts kTestRefreshToken and returns // kTestAccessToken + {"1", "2", "3", ...}. class TestAuthService : public DummyAuthService { public: TestAuthService() : auth_try_count_(0) {} void StartAuthentication(const AuthStatusCallback& callback) override { // RequestSender should clear the rejected access token before starting // to request another one. EXPECT_FALSE(HasAccessToken()); ++auth_try_count_; if (refresh_token() == kTestRefreshToken) { const std::string token = kTestAccessToken + base::IntToString(auth_try_count_); set_access_token(token); callback.Run(HTTP_SUCCESS, token); } else { set_access_token(""); callback.Run(HTTP_UNAUTHORIZED, ""); } } private: int auth_try_count_; }; // The main test fixture class. class RequestSenderTest : public testing::Test { protected: RequestSenderTest() : auth_service_(new TestAuthService), request_sender_(auth_service_, NULL, NULL, "dummy-user-agent") { auth_service_->set_refresh_token(kTestRefreshToken); auth_service_->set_access_token(kTestAccessToken); } TestAuthService* auth_service_; // Owned by |request_sender_|. RequestSender request_sender_; }; // Minimal implementation for AuthenticatedRequestInterface that can interact // with RequestSender correctly. class TestRequest : public AuthenticatedRequestInterface { public: TestRequest(RequestSender* sender, bool* start_called, FinishReason* finish_reason) : sender_(sender), start_called_(start_called), finish_reason_(finish_reason), weak_ptr_factory_(this) { } // Test the situation that the request has finished. void FinishRequestWithSuccess() { *finish_reason_ = SUCCESS; sender_->RequestFinished(this); } const std::string& passed_access_token() const { return passed_access_token_; } const ReAuthenticateCallback& passed_reauth_callback() const { return passed_reauth_callback_; } void Start(const std::string& access_token, const std::string& custom_user_agent, const ReAuthenticateCallback& callback) override { *start_called_ = true; passed_access_token_ = access_token; passed_reauth_callback_ = callback; // This request class itself does not return any response at this point. // Each test case should respond properly by using the above methods. } void Cancel() override { EXPECT_TRUE(*start_called_); *finish_reason_ = CANCEL; sender_->RequestFinished(this); } void OnAuthFailed(DriveApiErrorCode code) override { *finish_reason_ = AUTH_FAILURE; sender_->RequestFinished(this); } base::WeakPtr GetWeakPtr() override { return weak_ptr_factory_.GetWeakPtr(); } private: RequestSender* sender_; bool* start_called_; FinishReason* finish_reason_; std::string passed_access_token_; ReAuthenticateCallback passed_reauth_callback_; base::WeakPtrFactory weak_ptr_factory_; }; } // namespace TEST_F(RequestSenderTest, StartAndFinishRequest) { bool start_called = false; FinishReason finish_reason = NONE; TestRequest* request = new TestRequest(&request_sender_, &start_called, &finish_reason); base::WeakPtr weak_ptr = request->GetWeakPtr(); base::Closure cancel_closure = request_sender_.StartRequestWithAuthRetry(request); EXPECT_TRUE(!cancel_closure.is_null()); // Start is called with the specified access token. Let it succeed. EXPECT_TRUE(start_called); EXPECT_EQ(kTestAccessToken, request->passed_access_token()); request->FinishRequestWithSuccess(); EXPECT_FALSE(weak_ptr); // The request object is deleted. // It is safe to run the cancel closure even after the request is finished. // It is just no-op. The TestRequest::Cancel method is not called. cancel_closure.Run(); EXPECT_EQ(SUCCESS, finish_reason); } TEST_F(RequestSenderTest, StartAndCancelRequest) { bool start_called = false; FinishReason finish_reason = NONE; TestRequest* request = new TestRequest(&request_sender_, &start_called, &finish_reason); base::WeakPtr weak_ptr = request->GetWeakPtr(); base::Closure cancel_closure = request_sender_.StartRequestWithAuthRetry(request); EXPECT_TRUE(!cancel_closure.is_null()); EXPECT_TRUE(start_called); cancel_closure.Run(); EXPECT_EQ(CANCEL, finish_reason); EXPECT_FALSE(weak_ptr); // The request object is deleted. } TEST_F(RequestSenderTest, NoRefreshToken) { auth_service_->ClearRefreshToken(); auth_service_->ClearAccessToken(); bool start_called = false; FinishReason finish_reason = NONE; TestRequest* request = new TestRequest(&request_sender_, &start_called, &finish_reason); base::WeakPtr weak_ptr = request->GetWeakPtr(); base::Closure cancel_closure = request_sender_.StartRequestWithAuthRetry(request); EXPECT_TRUE(!cancel_closure.is_null()); // The request is not started at all because no access token is obtained. EXPECT_FALSE(start_called); EXPECT_EQ(AUTH_FAILURE, finish_reason); EXPECT_FALSE(weak_ptr); // The request object is deleted. } TEST_F(RequestSenderTest, ValidRefreshTokenAndNoAccessToken) { auth_service_->ClearAccessToken(); bool start_called = false; FinishReason finish_reason = NONE; TestRequest* request = new TestRequest(&request_sender_, &start_called, &finish_reason); base::WeakPtr weak_ptr = request->GetWeakPtr(); base::Closure cancel_closure = request_sender_.StartRequestWithAuthRetry(request); EXPECT_TRUE(!cancel_closure.is_null()); // Access token should indicate that this is the first retry. EXPECT_TRUE(start_called); EXPECT_EQ(kTestAccessToken + std::string("1"), request->passed_access_token()); request->FinishRequestWithSuccess(); EXPECT_EQ(SUCCESS, finish_reason); EXPECT_FALSE(weak_ptr); // The request object is deleted. } TEST_F(RequestSenderTest, AccessTokenRejectedSeveralTimes) { bool start_called = false; FinishReason finish_reason = NONE; TestRequest* request = new TestRequest(&request_sender_, &start_called, &finish_reason); base::WeakPtr weak_ptr = request->GetWeakPtr(); base::Closure cancel_closure = request_sender_.StartRequestWithAuthRetry(request); EXPECT_TRUE(!cancel_closure.is_null()); EXPECT_TRUE(start_called); EXPECT_EQ(kTestAccessToken, request->passed_access_token()); // Emulate the case that the access token was rejected by the remote service. request->passed_reauth_callback().Run(request); // New access token is fetched. Let it fail once again. EXPECT_EQ(kTestAccessToken + std::string("1"), request->passed_access_token()); request->passed_reauth_callback().Run(request); // Once more. EXPECT_EQ(kTestAccessToken + std::string("2"), request->passed_access_token()); request->passed_reauth_callback().Run(request); // Currently, limit for the retry is controlled in each request object, not // by the RequestSender. So with this TestRequest, RequestSender retries // infinitely. Let it succeed/ EXPECT_EQ(kTestAccessToken + std::string("3"), request->passed_access_token()); request->FinishRequestWithSuccess(); EXPECT_EQ(SUCCESS, finish_reason); EXPECT_FALSE(weak_ptr); } } // namespace google_apis