blob: b5a8524e79aa01ae58b0de07114eb2e3f5b4de03 [file] [log] [blame]
// Copyright 2014 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 "components/invalidation/impl/gcm_network_channel.h"
#include <memory>
#include <utility>
#include "base/base64url.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_network_connection_tracker.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace syncer {
namespace {
const char kURL[] = "http://test.url.com/";
}
class TestGCMNetworkChannelDelegate : public GCMNetworkChannelDelegate {
public:
TestGCMNetworkChannelDelegate()
: register_call_count_(0) {}
void Initialize(ConnectionStateCallback connection_state_callback,
base::Closure store_reset_callback) override {
this->connection_state_callback = connection_state_callback;
}
void RequestToken(RequestTokenCallback callback) override {
request_token_callback = callback;
}
void InvalidateToken(const std::string& token) override {
invalidated_token = token;
}
void Register(RegisterCallback callback) override {
++register_call_count_;
register_callback = callback;
}
void SetMessageReceiver(MessageCallback callback) override {
message_callback = callback;
}
RequestTokenCallback request_token_callback;
std::string invalidated_token;
RegisterCallback register_callback;
int register_call_count_;
MessageCallback message_callback;
ConnectionStateCallback connection_state_callback;
};
// Backoff policy for test. Run first 5 retries without delay.
const net::BackoffEntry::Policy kTestBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
5,
// Initial delay for exponential back-off in ms.
2000, // 2 seconds.
// Factor by which the waiting time will be multiplied.
2,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0.2, // 20%.
// Maximum amount of time we are willing to delay our request in ms.
1000 * 3600 * 4, // 4 hours.
// Time to keep an entry from being discarded even when it
// has no significant state, -1 to never discard.
-1,
// Don't use initial delay unless the last request was an error.
false,
};
class TestGCMNetworkChannel : public GCMNetworkChannel {
public:
TestGCMNetworkChannel(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
network::NetworkConnectionTracker* network_connection_tracker,
std::unique_ptr<GCMNetworkChannelDelegate> delegate)
: GCMNetworkChannel(std::move(url_loader_factory),
network_connection_tracker,
std::move(delegate)) {
ResetRegisterBackoffEntryForTest(&kTestBackoffPolicy);
}
protected:
// On Android GCMNetworkChannel::BuildUrl hits NOTREACHED(). I still want
// tests to run.
GURL BuildUrl(const std::string& registration_id) override {
return GURL(kURL);
}
};
class GCMNetworkChannelTest;
class GCMNetworkChannelTest
: public ::testing::Test,
public SyncNetworkChannel::Observer {
public:
GCMNetworkChannelTest()
: delegate_(nullptr),
last_invalidator_state_(TRANSIENT_INVALIDATION_ERROR),
test_shared_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {}
~GCMNetworkChannelTest() override {}
void SetUp() override {
network_request_count_ = 0;
test_url_loader_factory()->SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
EXPECT_EQ(kURL, request.url);
++network_request_count_;
request.headers.GetHeader("echo-token", &last_echo_token_);
}));
// Ownership of delegate goes to GCNMentworkChannel but test needs pointer
// to it.
delegate_ = new TestGCMNetworkChannelDelegate();
std::unique_ptr<GCMNetworkChannelDelegate> delegate(delegate_);
gcm_network_channel_.reset(new TestGCMNetworkChannel(
test_shared_loader_factory_,
network::TestNetworkConnectionTracker::GetInstance(),
std::move(delegate)));
gcm_network_channel_->AddObserver(this);
gcm_network_channel_->SetMessageReceiver(
invalidation::NewPermanentCallback(
this, &GCMNetworkChannelTest::OnIncomingMessage));
}
void TearDown() override { gcm_network_channel_->RemoveObserver(this); }
// Helper functions to call private methods from test
GURL BuildUrl(const std::string& registration_id) {
return gcm_network_channel_->GCMNetworkChannel::BuildUrl(registration_id);
}
void OnNetworkChannelStateChanged(
InvalidatorState invalidator_state) override {
last_invalidator_state_ = invalidator_state;
}
void OnIncomingMessage(std::string /* incoming_message */) {}
GCMNetworkChannel* network_channel() {
return gcm_network_channel_.get();
}
TestGCMNetworkChannelDelegate* delegate() {
return delegate_;
}
const std::string& get_last_echo_token() {
return last_echo_token_;
}
int get_network_request_count() { return network_request_count_; }
InvalidatorState get_last_invalidator_state() {
return last_invalidator_state_;
}
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
void RunLoopUntilIdle() {
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
private:
base::test::SingleThreadTaskEnvironment task_environment_;
TestGCMNetworkChannelDelegate* delegate_;
std::unique_ptr<GCMNetworkChannel> gcm_network_channel_;
int network_request_count_;
std::string last_echo_token_;
InvalidatorState last_invalidator_state_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
};
TEST_F(GCMNetworkChannelTest, HappyCase) {
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
EXPECT_FALSE(delegate()->message_callback.is_null());
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_NO_CONTENT);
// Emulate gcm connection state to be online.
delegate()->connection_state_callback.Run(true);
// After construction GCMNetworkChannel should have called Register.
EXPECT_FALSE(delegate()->register_callback.is_null());
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
network_channel()->SendMessage("abra.cadabra");
// SendMessage should have triggered RequestToken. No HTTP request should be
// started yet.
EXPECT_FALSE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
// Return another access token. Message should be cleared by now and shouldn't
// be sent.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token2");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
}
TEST_F(GCMNetworkChannelTest, FailedRegister) {
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_NO_CONTENT);
// After construction GCMNetworkChannel should have called Register.
EXPECT_FALSE(delegate()->register_callback.is_null());
EXPECT_EQ(1, delegate()->register_call_count_);
// Return transient error from Register call.
delegate()->register_callback.Run("", gcm::GCMClient::NETWORK_ERROR);
RunLoopUntilIdle();
// GcmNetworkChannel should have scheduled Register retry.
EXPECT_EQ(2, delegate()->register_call_count_);
// Return persistent error from Register call.
delegate()->register_callback.Run("", gcm::GCMClient::GCM_DISABLED);
RunLoopUntilIdle();
// GcmNetworkChannel should give up trying.
EXPECT_EQ(2, delegate()->register_call_count_);
network_channel()->SendMessage("abra.cadabra");
// SendMessage shouldn't trigger RequestToken.
EXPECT_TRUE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
}
TEST_F(GCMNetworkChannelTest, RegisterFinishesAfterSendMessage) {
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_NO_CONTENT);
// After construction GCMNetworkChannel should have called Register.
EXPECT_FALSE(delegate()->register_callback.is_null());
network_channel()->SendMessage("abra.cadabra");
// SendMessage shouldn't trigger RequestToken.
EXPECT_TRUE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
EXPECT_FALSE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
}
TEST_F(GCMNetworkChannelTest, RequestTokenFailure) {
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_NO_CONTENT);
// After construction GCMNetworkChannel should have called Register.
EXPECT_FALSE(delegate()->register_callback.is_null());
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
network_channel()->SendMessage("abra.cadabra");
// SendMessage should have triggered RequestToken. No HTTP request should be
// started yet.
EXPECT_FALSE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
// RequestToken returns failure.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::FromConnectionError(1), "");
// Should be no HTTP requests.
EXPECT_EQ(get_network_request_count(), 0);
}
TEST_F(GCMNetworkChannelTest, AuthErrorFromServer) {
// Setup fake response to return AUTH_ERROR.
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_UNAUTHORIZED);
// After construction GCMNetworkChannel should have called Register.
EXPECT_FALSE(delegate()->register_callback.is_null());
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
network_channel()->SendMessage("abra.cadabra");
// SendMessage should have triggered RequestToken. No HTTP request should be
// started yet.
EXPECT_FALSE(delegate()->request_token_callback.is_null());
EXPECT_EQ(get_network_request_count(), 0);
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
EXPECT_EQ(delegate()->invalidated_token, "access.token");
}
// Following two tests are to check for memory leaks/crashes when Register and
// RequestToken don't complete by the teardown.
TEST_F(GCMNetworkChannelTest, RegisterNeverCompletes) {
network_channel()->SendMessage("abra.cadabra");
// Register should be called by now. Let's not complete and see what happens.
EXPECT_FALSE(delegate()->register_callback.is_null());
}
TEST_F(GCMNetworkChannelTest, RequestTokenNeverCompletes) {
network_channel()->SendMessage("abra.cadabra");
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
// RequestToken should be called by now. Let's not complete and see what
// happens.
EXPECT_FALSE(delegate()->request_token_callback.is_null());
}
TEST_F(GCMNetworkChannelTest, ChannelState) {
EXPECT_FALSE(delegate()->message_callback.is_null());
// POST will fail.
test_url_loader_factory()->AddResponse(kURL, "",
net::HTTP_SERVICE_UNAVAILABLE);
delegate()->connection_state_callback.Run(true);
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
network_channel()->SendMessage("abra.cadabra");
EXPECT_FALSE(delegate()->request_token_callback.is_null());
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
// Failing HTTP POST should cause TRANSIENT_INVALIDATION_ERROR.
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
// Setup POST to succeed.
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_NO_CONTENT);
network_channel()->SendMessage("abra.cadabra");
EXPECT_FALSE(delegate()->request_token_callback.is_null());
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 2);
// Successful post should set invalidator state to enabled.
EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
// Network changed event shouldn't affect invalidator state.
network_channel()->OnConnectionChanged(
network::mojom::ConnectionType::CONNECTION_NONE);
EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
// GCM connection state should affect invalidator state.
delegate()->connection_state_callback.Run(false);
EXPECT_EQ(TRANSIENT_INVALIDATION_ERROR, get_last_invalidator_state());
delegate()->connection_state_callback.Run(true);
EXPECT_EQ(INVALIDATIONS_ENABLED, get_last_invalidator_state());
}
#if !defined(OS_ANDROID)
TEST_F(GCMNetworkChannelTest, BuildUrl) {
GURL url = BuildUrl("registration.id");
EXPECT_TRUE(url.SchemeIsHTTPOrHTTPS());
EXPECT_FALSE(url.host().empty());
EXPECT_FALSE(url.path().empty());
std::vector<std::string> parts = base::SplitString(
url.path(), "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
std::string buffer;
EXPECT_TRUE(base::Base64UrlDecode(parts.back(),
base::Base64UrlDecodePolicy::IGNORE_PADDING,
&buffer));
}
TEST_F(GCMNetworkChannelTest, EchoToken) {
test_url_loader_factory()->AddResponse(kURL, "", net::HTTP_OK);
// After construction GCMNetworkChannel should have called Register.
// Return valid registration id.
delegate()->register_callback.Run("registration.id", gcm::GCMClient::SUCCESS);
network_channel()->SendMessage("abra.cadabra");
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 1);
EXPECT_TRUE(get_last_echo_token().empty());
// Trigger response.
delegate()->message_callback.Run("abra.cadabra", "echo.token");
// Send another message.
network_channel()->SendMessage("abra.cadabra");
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 2);
EXPECT_EQ("echo.token", get_last_echo_token());
// Trigger response with empty echo token.
delegate()->message_callback.Run("abra.cadabra", "");
// Send another message.
network_channel()->SendMessage("abra.cadabra");
// Return valid access token. This should trigger HTTP request.
delegate()->request_token_callback.Run(
GoogleServiceAuthError::AuthErrorNone(), "access.token");
RunLoopUntilIdle();
EXPECT_EQ(get_network_request_count(), 3);
// Echo_token should be from second message.
EXPECT_EQ("echo.token", get_last_echo_token());
}
#endif
} // namespace syncer