blob: 53ea7d122aafb82c45985fa127a77682f41af2ab [file]
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <queue>
#include "google/cacheinvalidation/gmock.h"
#include "google/cacheinvalidation/googletest.h"
#include "google/cacheinvalidation/invalidation-client-impl.h"
#include "google/cacheinvalidation/logging.h"
#include "google/cacheinvalidation/proto-converter.h"
#include "google/cacheinvalidation/random.h"
#include "google/cacheinvalidation/scoped_ptr.h"
#include "google/cacheinvalidation/stl-namespace.h"
#include "google/cacheinvalidation/system-resources-for-test.h"
#include "google/cacheinvalidation/version-manager.h"
namespace invalidation {
using INVALIDATION_STL_NAMESPACE::make_pair;
using INVALIDATION_STL_NAMESPACE::pair;
using INVALIDATION_STL_NAMESPACE::vector;
using ::testing::_;
using ::testing::AllOf;
using ::testing::InvokeArgument;
using ::testing::MakeMatcher;
using ::testing::Matcher;
using ::testing::MatcherInterface;
using ::testing::MatchResultListener;
using ::testing::Property;
using ::testing::SaveArg;
using ::testing::StrictMock;
class MockSystemResources : public SystemResourcesForTest {
public:
MOCK_METHOD2(WriteState, void(const string&, StorageCallback*));
};
/* A listener for testing. */
class MockListener : public InvalidationListener {
public:
MOCK_METHOD2(Invalidate, void(const Invalidation&, Closure*));
MOCK_METHOD1(InvalidateAll, void(Closure*));
MOCK_METHOD1(AllRegistrationsLost, void(Closure*));
MOCK_METHOD3(RegistrationStateChanged,
void(const ObjectId&, RegistrationState, const UnknownHint&));
MOCK_METHOD1(SessionStatusChanged, void(bool));
/* System resources, for checking that callbacks run on the right thread. */
SystemResources* resources_;
};
static bool ObjectIdPsEqual(const ObjectIdP& object_id1,
const ObjectIdP& object_id2) {
return (object_id1.source() == object_id2.source()) &&
(object_id1.name().string_value() == object_id2.name().string_value());
}
static bool ObjectIdsEqual(const ObjectId& object_id1,
const ObjectId& object_id2) {
return (object_id1.source() == object_id2.source()) &&
(object_id1.name() == object_id2.name());
}
class ObjectIdEqMatcher : public MatcherInterface<const ObjectId&> {
public:
explicit ObjectIdEqMatcher(const ObjectId& object_id)
: object_id_(object_id) {}
virtual bool MatchAndExplain(const ObjectId& other_oid,
MatchResultListener* listener) const {
return ObjectIdsEqual(object_id_, other_oid);
}
virtual void DescribeTo(::std::ostream* os) const {
*os << "object ids equal";
}
private:
ObjectId object_id_;
};
inline Matcher<const ObjectId&> ObjectIdEq(const ObjectId& object_id) {
return MakeMatcher(new ObjectIdEqMatcher(object_id));
}
class InvalidationClientImplTest : public testing::Test {
public:
InvalidationClientImplTest() :
// Calls to the outbound network listener are throttled to no more than
// one per second, so sometimes we need to advance time by this much in
// order for the next call to be made.
fine_throttle_interval_(TimeDelta::FromSeconds(1)),
default_registration_timeout_(TimeDelta::FromMinutes(1)) {}
/* A name for the application. */
static const char* APP_NAME;
/* Fake client information for testing. */
static const char* CLIENT_INFO;
/* Fake data for a session token. */
static const char* OPAQUE_DATA;
/* A status object indicating success. */
Status success_status_;
/* An object id. */
ObjectIdP object_id1_;
/* An object id. */
ObjectIdP object_id2_;
/* A sample version. */
static const int64 VERSION;
/* System resources for testing. */
scoped_ptr<MockSystemResources> resources_;
/* Test listener. */
scoped_ptr<MockListener> listener_;
/* The invalidation client being tested. */
scoped_ptr<InvalidationClientImpl> ticl_;
/* A field that's set when the Ticl informs us about an outgoing message.
*/
bool outbound_message_ready_;
/* Listens for outbound messages from the Ticl. */
void HandleOutboundMessageReady(NetworkEndpoint* const& endpoint) {
ASSERT_FALSE(resources_->IsRunningOnInternalThread());
outbound_message_ready_ = true;
}
scoped_ptr<NetworkCallback> network_listener_;
/* The uniquifier that we've assigned for the client. */
string client_uniquifier_;
/* The session token we've assigned for the client. */
string session_token_;
/* A register operation. */
RegistrationUpdate reg_op1_;
/* A register operation. */
RegistrationUpdate reg_op2_;
/* Registration responses we've received. */
vector<RegistrationUpdateResult> reg_results_;
/* The throttler's smaller window size. */
TimeDelta fine_throttle_interval_;
/* The default registration timeout. */
TimeDelta default_registration_timeout_;
/* The last state the Ticl persisted. */
string last_persisted_state_;
/* Checks that client's message contains a proper id-assignment request. */
void CheckAssignClientIdRequest(
const ClientToServerMessage& message, ClientExternalIdP* result) {
// Check that the message contains an "assign client id" action.
ASSERT_TRUE(message.has_action());
ASSERT_EQ(message.action(), ClientToServerMessage_Action_ASSIGN_CLIENT_ID);
// Check that the message specifies the client's desired protocol version.
ProtocolVersion expected_version;
VersionManager::GetLatestProtocolVersion(&expected_version);
ASSERT_EQ(message.protocol_version().version().major_version(),
expected_version.version().major_version());
ASSERT_EQ(message.protocol_version().version().minor_version(),
expected_version.version().minor_version());
// Check that the message specifies the client's version.
ClientVersion expected_client_version;
VersionManager version_manager(CLIENT_INFO);
version_manager.GetClientVersion(&expected_client_version);
ASSERT_EQ(message.client_version().version().major_version(),
expected_client_version.version().major_version());
ASSERT_EQ(message.client_version().version().minor_version(),
expected_client_version.version().minor_version());
ASSERT_EQ(message.client_version().flavor(),
expected_client_version.flavor());
ASSERT_EQ(message.client_version().client_info(), CLIENT_INFO);
// Check that the message supplied a timestamp.
ASSERT_EQ(message.timestamp(),
static_cast<uint64>(
resources_->current_time().ToInternalValue() /
Time::kMicrosecondsPerMillisecond));
// Check that the message contains an "assign client id" type.
ASSERT_TRUE(message.has_message_type());
ASSERT_EQ(message.message_type(),
ClientToServerMessage_MessageType_TYPE_ASSIGN_CLIENT_ID);
// Check that it does not contain a session token or any registration
// operations or invalidation acknowledgments.
ASSERT_FALSE(message.has_session_token());
ASSERT_EQ(message.acked_invalidation_size(), 0);
ASSERT_EQ(message.register_operation_size(), 0);
// Check that it contains the fields of an external id.
ASSERT_TRUE(message.has_client_type());
ASSERT_EQ(message.client_type().type(), ClientType_Type_CHROME_SYNC);
ASSERT_TRUE(message.has_app_client_id());
ASSERT_EQ(message.app_client_id().string_value(), APP_NAME);
// Check that the client did not specify values for the server-supplied
// fields.
ASSERT_FALSE(message.has_session_token());
result->mutable_client_type()->CopyFrom(message.client_type());
result->mutable_app_client_id()->CopyFrom(message.app_client_id());
}
void TestInitialization() {
// Start up the Ticl, connect a network listener, and let it do its
// initialization.
ticl_->Start("");
outbound_message_ready_ = false;
ticl_->network_endpoint()->RegisterOutboundListener(
network_listener_.get());
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Check that it has a message to send, and pull the message.
ASSERT_TRUE(outbound_message_ready_);
outbound_message_ready_ = false;
string serialized;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
ClientToServerMessage message;
message.ParseFromString(serialized);
// Check that the message is a proper request for client id assignment.
ClientExternalIdP external_id;
CheckAssignClientIdRequest(message, &external_id);
// Construct a uniquifier.
client_uniquifier_ = "uniquifier";
// Also construct an initial session token.
session_token_ = OPAQUE_DATA;
// Construct a response with the uniquifier and session token.
ServerToClientMessage response;
response.mutable_client_type()->set_type(external_id.client_type().type());
response.mutable_app_client_id()->set_string_value(
external_id.app_client_id().string_value());
response.set_nonce(message.nonce());
response.set_client_uniquifier(client_uniquifier_);
response.set_session_token(session_token_);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_ASSIGN_CLIENT_ID);
response.SerializeToString(&serialized);
EXPECT_CALL(*listener_, SessionStatusChanged(true));
Closure* callback = NULL;
EXPECT_CALL(*listener_, AllRegistrationsLost(_))
.WillOnce(SaveArg<0>(&callback));
// Give the message to the Ticl, and let it handle it.
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(callback != NULL);
callback->Run();
delete callback;
StorageCallback* storage_callback = NULL;
EXPECT_CALL(*resources_, WriteState(_, _))
.WillOnce(DoAll(SaveArg<0>(&last_persisted_state_),
SaveArg<1>(&storage_callback)));
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
storage_callback->Run(true);
delete storage_callback;
}
/* Requests that the Ticl (un)register for two objects. Checks that the
* message it sends contains the correct information about these
* (un)registrations.
*/
void MakeAndCheckRegistrations(bool is_register) {
void (InvalidationClient::*operation)(const ObjectId&) =
is_register ?
&InvalidationClient::Register : &InvalidationClient::Unregister;
// Ask the Ticl to register for two objects.
outbound_message_ready_ = false;
ObjectId oid1;
ObjectId oid2;
ConvertFromObjectIdProto(object_id1_, &oid1);
ConvertFromObjectIdProto(object_id2_, &oid2);
(ticl_.get()->*operation)(oid1);
(ticl_.get()->*operation)(oid2);
resources_->ModifyTime(fine_throttle_interval_);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
RegistrationUpdate_Type operation_type = is_register ?
RegistrationUpdate_Type_REGISTER : RegistrationUpdate_Type_UNREGISTER;
// Pull a message, and check that it has the right session token and
// registration update messages.
ClientToServerMessage message;
string serialized;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
ASSERT_TRUE(message.has_session_token());
ASSERT_EQ(message.session_token(), session_token_);
ASSERT_TRUE(message.has_message_type());
ASSERT_EQ(message.message_type(),
ClientToServerMessage_MessageType_TYPE_OBJECT_CONTROL);
ASSERT_EQ(message.register_operation_size(), 2);
reg_op1_.Clear();
reg_op1_.mutable_object_id()->CopyFrom(object_id1_);
reg_op1_.set_sequence_number(1);
reg_op1_.set_type(operation_type);
reg_op2_.mutable_object_id()->CopyFrom(object_id2_);
reg_op2_.set_sequence_number(2);
reg_op2_.set_type(operation_type);
string serialized2, serialized_reg_op1, serialized_reg_op2;
reg_op1_.SerializeToString(&serialized_reg_op1);
message.register_operation(0).SerializeToString(&serialized);
reg_op2_.SerializeToString(&serialized_reg_op2);
message.register_operation(1).SerializeToString(&serialized2);
ASSERT_TRUE(((serialized == serialized_reg_op1) &&
(serialized2 == serialized_reg_op2)) ||
((serialized == serialized_reg_op2) &&
(serialized2 == serialized_reg_op1)));
// Check that the Ticl has not responded to the app about either of the
// operations yet.
ASSERT_TRUE(reg_results_.empty());
}
void TestRegistration(bool is_register) {
// Do setup and initiate registrations.
TestInitialization();
outbound_message_ready_ = false;
MakeAndCheckRegistrations(is_register);
// Let some time pass to allow the periodic task to run before the responses
// are received. This catches a bug where the Ticl was issuing a timeout
// too early.
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Construct responses and let the Ticl process them.
ServerToClientMessage response;
RegistrationUpdateResult* result1 = response.add_registration_result();
result1->mutable_operation()->CopyFrom(reg_op1_);
result1->mutable_status()->set_code(Status_Code_SUCCESS);
RegistrationUpdateResult* result2 = response.add_registration_result();
result2->mutable_operation()->CopyFrom(reg_op2_);
result2->mutable_status()->set_code(Status_Code_SUCCESS);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.set_session_token(session_token_);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
response.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Check that the registration state is REGISTERED.
ASSERT_EQ(RegState_REGISTERED,
ticl_->GetRegistrationStateForTest(object_id1_));
ASSERT_EQ(RegState_REGISTERED,
ticl_->GetRegistrationStateForTest(object_id2_));
// Advance the clock a lot, run everything, and make sure it's not trying to
// resend.
resources_->ModifyTime(default_registration_timeout_);
resources_->RunReadyTasks();
ClientToServerMessage message;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
ASSERT_EQ(message.register_operation_size(), 0);
}
void TestSessionSwitch() {
TestRegistration(true);
// Clear the "outbound message ready" flag, so we can check below that the
// invalid session status causes it to be set.
outbound_message_ready_ = false;
EXPECT_CALL(*listener_, SessionStatusChanged(false));
// Tell the Ticl its session is invalid.
ServerToClientMessage message;
message.set_session_token(session_token_);
message.mutable_status()->set_code(Status_Code_INVALID_SESSION);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_INVALIDATE_SESSION);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->ModifyTime(fine_throttle_interval_);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Check that the Ticl has pinged the client to indicate it has a request.
ASSERT_TRUE(outbound_message_ready_);
// Pull a message from the Ticl and check that it requests a new session.
ClientToServerMessage request;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
request.ParseFromString(serialized);
ASSERT_TRUE(request.has_action());
ASSERT_EQ(request.action(), ClientToServerMessage_Action_UPDATE_SESSION);
ASSERT_TRUE(request.has_message_type());
ASSERT_EQ(request.message_type(),
ClientToServerMessage_MessageType_TYPE_UPDATE_SESSION);
ASSERT_TRUE(request.has_client_uniquifier());
ASSERT_EQ(client_uniquifier_, request.client_uniquifier());
// Give it a new session token.
Closure* callback = NULL;
EXPECT_CALL(*listener_, AllRegistrationsLost(_))
.WillOnce(SaveArg<0>(&callback));
EXPECT_CALL(*listener_, SessionStatusChanged(true));
session_token_ = "NEW_OPAQUE_DATA";
message.Clear();
message.set_client_uniquifier(client_uniquifier_);
message.set_session_token(session_token_);
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_UPDATE_SESSION);
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
callback->Run();
delete callback;
// Give it some time and check that it persists the state.
StorageCallback* storage_callback = NULL;
EXPECT_CALL(*resources_, WriteState(_, _))
.WillOnce(DoAll(SaveArg<0>(&last_persisted_state_),
SaveArg<1>(&storage_callback)));
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
storage_callback->Run(true);
delete storage_callback;
}
void TestInvalidateAndReassignClientId() {
// Tell the Ticl we don't recognize it.
EXPECT_CALL(*listener_, SessionStatusChanged(false));
ServerToClientMessage message;
message.Clear();
message.mutable_status()->set_code(Status_Code_UNKNOWN_CLIENT);
message.set_session_token(session_token_);
string serialized;
message.set_client_uniquifier(client_uniquifier_);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_INVALIDATE_CLIENT_ID);
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
// Pull a message from it, and check that it's trying to assign a client id.
ClientToServerMessage request;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
request.ParseFromString(serialized);
ASSERT_TRUE(request.has_action());
ASSERT_EQ(request.action(), ClientToServerMessage_Action_ASSIGN_CLIENT_ID);
ClientExternalIdP external_id;
CheckAssignClientIdRequest(request, &external_id);
// Give it a new uniquifier and session.
string new_uniquifier_str = "newuniquifierstr";
session_token_ = "new opaque data";
ServerToClientMessage response;
response.set_session_token(session_token_);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.mutable_client_type()->set_type(external_id.client_type().type());
response.mutable_app_client_id()->set_string_value(
external_id.app_client_id().string_value());
response.set_nonce(request.nonce());
response.set_client_uniquifier(new_uniquifier_str);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_ASSIGN_CLIENT_ID);
response.SerializeToString(&serialized);
Closure* callback = NULL;
EXPECT_CALL(*listener_, AllRegistrationsLost(_))
.WillOnce(SaveArg<0>(&callback));
EXPECT_CALL(*listener_, SessionStatusChanged(true));
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
callback->Run();
delete callback;
}
virtual void SetUp() {
object_id1_.Clear();
object_id1_.set_source(ObjectIdP_Source_CHROME_SYNC);
object_id1_.mutable_name()->set_string_value("BOOKMARKS");
object_id2_.Clear();
object_id2_.set_source(ObjectIdP_Source_CHROME_SYNC);
object_id2_.mutable_name()->set_string_value("HISTORY");
resources_.reset(new StrictMock<MockSystemResources>());
resources_->ModifyTime(TimeDelta::FromSeconds(1000000));
resources_->StartScheduler();
listener_.reset(new StrictMock<MockListener>());
network_listener_.reset(
NewPermanentCallback(
this, &InvalidationClientImplTest::HandleOutboundMessageReady));
ClientConfig ticl_config;
ticl_config.smear_factor = 0.0; // Disable smearing for determinism.
ClientType client_type;
client_type.set_type(ClientType_Type_CHROME_SYNC);
ticl_.reset(new InvalidationClientImpl(
resources_.get(), client_type, APP_NAME, CLIENT_INFO, ticl_config,
listener_.get()));
reg_results_.clear();
}
virtual void TearDown() {
resources_->StopScheduler();
}
};
const char* InvalidationClientImplTest::APP_NAME = "app_name";
const char* InvalidationClientImplTest::CLIENT_INFO = "unit test client";
const char* InvalidationClientImplTest::OPAQUE_DATA = "opaque_data";
const int64 InvalidationClientImplTest::VERSION = 5;
TEST_F(InvalidationClientImplTest, Initialization) {
/* Test plan: start up a new Ticl. Check that it requests to send a message
* and that the message requests client id assignment with an appropriately
* formed partial client id. Respond with a full client id and session token.
* Check that the Ticl's next step is to poll invalidations.
*/
TestInitialization();
}
TEST_F(InvalidationClientImplTest, MismatchingClientIdIgnored) {
/* Test plan: create a Ticl and pull a bundle from it, which will be
* requesting a client id. Respond with a client id, but for a mismatched app
* client id. Check that pulling a subsequent bundle results in another
* assign-client-id action.
*/
// Start up the Ticl, connect a network listener, and let it do its
// initialization.
ticl_->Start("");
ticl_->network_endpoint()->RegisterOutboundListener(network_listener_.get());
resources_->RunReadyTasks();
// Pull a message.
ClientToServerMessage message;
string serialized;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
// Check that the message is a proper request for client id assignment.
ClientExternalIdP external_id;
CheckAssignClientIdRequest(message, &external_id);
// Fabricate a uniquifier and initial session token.
client_uniquifier_ = "uniquifier";
session_token_ = OPAQUE_DATA;
// Construct a response with the uniquifier and session token but the wrong
// app client id.
ServerToClientMessage response;
response.mutable_client_type()->CopyFrom(external_id.client_type());
response.mutable_app_client_id()->set_string_value("wrong-app-client-id");
response.set_client_uniquifier(client_uniquifier_);
response.set_session_token(session_token_);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.SerializeToString(&serialized);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_ASSIGN_CLIENT_ID);
// Give the message to the Ticl, and let it handle it.
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
// Pull a message.
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
// Check that the Ticl is still looking for a client id.
CheckAssignClientIdRequest(message, &external_id);
}
TEST_F(InvalidationClientImplTest, HeartbeatIntervalRespected) {
/* Test plan: get a client id and session, and consume the initial
* poll-invalidations message. Respond and increase heartbeat interval to
* 80s. Check that the outbound message listener doesn't get pinged until 80s
* in the future. Then send a message reducing the heartbeat interval to 10s.
* Because of the way the heartbeat timer is implemented, we don't expect the
* very next heartbeat to occur until 80s in the future, but subsequently it
* should be 10s.
*/
// Do setup.
TestInitialization();
// Respond with a new heartbeat interval (larger than the default).
int new_heartbeat_interval_ms = 300000;
ServerToClientMessage response;
response.set_session_token(session_token_);
response.set_next_heartbeat_interval_ms(new_heartbeat_interval_ms);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
response.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
outbound_message_ready_ = false;
// Advance to just shy of the heartbeat interval, and check that the Ticl did
// not nudge the application to send.
resources_->ModifyTime(
TimeDelta::FromMilliseconds(new_heartbeat_interval_ms - 1));
resources_->RunReadyTasks();
ASSERT_FALSE(outbound_message_ready_);
// Advance further, and check that it did nudge the application to send.
resources_->ModifyTime(fine_throttle_interval_);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
// Shorten the heartbeat interval and repeat.
response.Clear();
response.set_session_token(session_token_);
response.set_next_heartbeat_interval_ms(10000);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
response.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
outbound_message_ready_ = false;
// Because the Ticl uses a single timer-task, the next heartbeat will still
// happen after the longer interval.
// Periodic task executes after this since heartbeat interval is large.
resources_->ModifyTime(TimeDelta::FromMilliseconds(80000));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
outbound_message_ready_ = false;
// But subsequently, heartbeats should happen with the shorter interval.
resources_->ModifyTime(TimeDelta::FromMilliseconds(9999));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_FALSE(outbound_message_ready_);
resources_->ModifyTime(fine_throttle_interval_);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
}
TEST_F(InvalidationClientImplTest, Registration) {
/* Test plan: get a client id and session. Register for an object. Check
* that the Ticl sends an appropriate registration request. Respond with a
* successful status. Check that the registration callback is invoked with an
* appropriate result, and that the Ticl does not resend the request.
*/
TestRegistration(true);
}
TEST_F(InvalidationClientImplTest, Unegistration) {
/* Test plan: get a client id and session. Unregister for an object. Check
* that the Ticl sends an appropriate unregistration request. Respond with a
* successful status. Check that the unregistration callback is invoked with
* an appropriate result, and that the Ticl does not resend the request.
*/
// Start in the REGISTERED state so we actually have something to do.
TestRegistration(true);
ObjectId oid2;
ConvertFromObjectIdProto(object_id2_, &oid2);
ticl_->Unregister(oid2);
resources_->ModifyTime(
fine_throttle_interval_ + TimeDelta::FromMilliseconds(500));
resources_->RunReadyTasks();
// Pull a message, and check that it has the right session token and
// registration update message.
ClientToServerMessage message;
string serialized;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
ASSERT_TRUE(message.has_session_token());
ASSERT_EQ(message.session_token(), session_token_);
ASSERT_TRUE(message.has_message_type());
ASSERT_EQ(message.message_type(),
ClientToServerMessage_MessageType_TYPE_OBJECT_CONTROL);
ASSERT_EQ(message.register_operation_size(), 1);
const RegistrationUpdate& op = message.register_operation(0);
ASSERT_TRUE(ObjectIdPsEqual(object_id2_, op.object_id()));
ASSERT_EQ(RegistrationUpdate_Type_UNREGISTER, op.type());
// Construct a response.
ServerToClientMessage response;
response.set_session_token(session_token_);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
RegistrationUpdateResult* result = response.add_registration_result();
result->mutable_operation()->CopyFrom(op);
result->mutable_status()->set_code(Status_Code_SUCCESS);
response.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
}
TEST_F(InvalidationClientImplTest, RegistrationFailure) {
/* Test plan: get a client id and session. Register for an object. Check
* that the Ticl sends an appropriate registration request. Respond with an
* error status. Check that the registration callback is invoked with an
* appropriate result, and that the Ticl does not resend the request.
*/
// Do setup and initiate registrations.
TestInitialization();
outbound_message_ready_ = false;
MakeAndCheckRegistrations(true);
// Construct and deliver responses: one failure and one success.
ServerToClientMessage response;
RegistrationUpdateResult* result1 = response.add_registration_result();
result1->mutable_operation()->CopyFrom(reg_op1_);
result1->mutable_status()->set_code(Status_Code_PERMANENT_FAILURE);
result1->mutable_status()->set_description("Registration update failed");
RegistrationUpdateResult* result2 = response.add_registration_result();
result2->mutable_operation()->CopyFrom(reg_op2_);
result2->mutable_status()->set_code(Status_Code_SUCCESS);
response.mutable_status()->set_code(Status_Code_SUCCESS);
response.set_session_token(session_token_);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
response.SerializeToString(&serialized);
ObjectId oid1;
ConvertFromObjectIdProto(object_id1_, &oid1);
EXPECT_CALL(
*listener_,
RegistrationStateChanged(
ObjectIdEq(oid1), RegistrationState_UNKNOWN, _));
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_EQ(RegState_REGISTERED,
ticl_->GetRegistrationStateForTest(object_id2_));
// Advance the clock a lot, run everything, and make sure it's not trying to
// resend.
resources_->ModifyTime(default_registration_timeout_);
resources_->RunReadyTasks();
ClientToServerMessage message;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
ASSERT_EQ(message.register_operation_size(), 0);
}
TEST_F(InvalidationClientImplTest, InvalidationP) {
/* Test plan: get a client id and session token, and register for an object.
* Deliver an invalidation for that object. Check that the listener's
* invalidate() method gets called with the right invalidation. Check that
* the Ticl acks the invalidation, but only after the listener has acked it.
*/
TestRegistration(true);
Closure* callback = NULL;
ObjectId oid1;
ConvertFromObjectIdProto(object_id1_, &oid1);
EXPECT_CALL(*listener_, Invalidate(AllOf(
Property(&Invalidation::version, InvalidationClientImplTest::VERSION),
Property(&Invalidation::object_id, ObjectIdEq(oid1))), _))
.WillOnce(SaveArg<1>(&callback));
// Deliver an invalidation for an object.
ServerToClientMessage message;
InvalidationP* invalidation = message.add_invalidation();
invalidation->mutable_object_id()->CopyFrom(object_id1_);
invalidation->set_version(InvalidationClientImplTest::VERSION);
message.set_session_token(session_token_);
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Check that the Ticl isn't acking the invalidation yet, since we haven't
// called the callback.
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
ClientToServerMessage client_message;
client_message.ParseFromString(serialized);
ASSERT_EQ(client_message.acked_invalidation_size(), 0);
outbound_message_ready_ = false;
// Now run the callback, and check that the Ticl does ack the invalidation.
callback->Run();
delete callback;
resources_->ModifyTime(fine_throttle_interval_);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
client_message.ParseFromString(serialized);
ASSERT_EQ(1, client_message.acked_invalidation_size());
ASSERT_EQ(static_cast<uint64>(InvalidationClientImplTest::VERSION),
client_message.acked_invalidation(0).version());
ASSERT_TRUE(
ObjectIdPsEqual(object_id1_,
client_message.acked_invalidation(0).object_id()));
}
TEST_F(InvalidationClientImplTest, SessionSwitch) {
/* Test plan: get client id and session. Register for a couple of objects.
* Send the Ticl an invalid-session message. Check that the Ticl sends an
* UpdateSession request, and respond with a new session token and last
* sequence number of 1. Check that the Ticl resends a registration request
* for the second register operation.
*/
TestSessionSwitch();
}
TEST_F(InvalidationClientImplTest, MismatchingInvalidSessionIgnored) {
/* Test plan: get client id and session. Register for a couple of objects.
* Send the Ticl an invalid-session message with a mismatched session token.
* Check that the Ticl ignores it.
*/
TestRegistration(true);
// Tell the Ticl its session is invalid.
string bogus_session_token = "bogus-session-token";
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_INVALID_SESSION);
message.set_session_token(bogus_session_token);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_INVALIDATE_SESSION);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
// Pull a message from the Ticl and check that it doesn't request a new
// session.
ClientToServerMessage request;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
request.ParseFromString(serialized);
ASSERT_FALSE(request.has_action());
}
TEST_F(InvalidationClientImplTest, GarbageCollection) {
/* Test plan: get a client id and session, and perform some registrations.
* Send the Ticl a message indicating it has been garbage-collected. Check
* that the Ticl requests a new client id. Respond with one, along with a
* session. Check that it repeats the register operations, and that it sends
* an invalidateAll once the registrations have completed.
*/
TestRegistration(true);
TestInvalidateAndReassignClientId();
}
TEST_F(InvalidationClientImplTest, LoseSessionThenClientId) {
/* Test plan: get a client is and session. Send a message indicating the
* session is invalid. When it asks to update the session, send another
* message indicating the client id is invalid. Check that it then behaves
* like a fresh client (makes an assign-client-id request, etc.).
*/
TestInitialization();
// Tell the Ticl we don't recognize its session.
EXPECT_CALL(*listener_, SessionStatusChanged(false));
ServerToClientMessage message;
message.Clear();
message.mutable_status()->set_code(Status_Code_INVALID_SESSION);
message.set_session_token(session_token_);
string serialized;
message.set_client_uniquifier(client_uniquifier_);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_INVALIDATE_SESSION);
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Pull a message from it, and check that it's trying to update its session.
ClientToServerMessage request;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
request.ParseFromString(serialized);
ASSERT_TRUE(request.has_action());
ASSERT_EQ(request.action(), ClientToServerMessage_Action_UPDATE_SESSION);
TestInvalidateAndReassignClientId();
}
TEST_F(InvalidationClientImplTest, MismatchedUnknownClientIgnored) {
/* Test plan: get a client id and session, and perform some registrations.
* Send the Ticl a message indicating it has been garbage-collected, with a
* mismatched client id. Check that the Ticl ignores it.
*/
TestRegistration(true);
// Tell the Ticl we don't recognize it, but supply an incorrect client id.
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_UNKNOWN_CLIENT);
message.set_session_token(session_token_);
message.set_client_uniquifier("bogus-client-id");
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_INVALIDATE_CLIENT_ID);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
// Pull a message from it, and check that it's not trying to assign a client
// id.
ClientToServerMessage request;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
request.ParseFromString(serialized);
ASSERT_FALSE(request.has_action());
}
TEST_F(InvalidationClientImplTest, InvalidateAll) {
/* Test plan: initialize the Ticl. Send it a message with the "invalidate
* all" object id, and check that the app gets an invalidateAll() call.
*/
TestInitialization();
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_session_token(session_token_);
InvalidationP* inv = message.add_invalidation();
inv->mutable_object_id()->set_source(ObjectIdP_Source_INTERNAL);
inv->mutable_object_id()->mutable_name()->set_string_value("ALL");
inv->set_version(1);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
message.SerializeToString(&serialized);
Closure* callback = NULL;
EXPECT_CALL(*listener_, InvalidateAll(_))
.WillOnce(SaveArg<0>(&callback));
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
callback->Run();
delete callback;
}
TEST_F(InvalidationClientImplTest, AcceptsProtocolVersion1) {
/* Test plan: initialize the Ticl. Send it a message with the "invalidate
* all" object id and protocol version 1, and check that the app gets an
* invalidateAll() call.
*/
TestInitialization();
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_session_token(session_token_);
message.mutable_protocol_version()->mutable_version()->set_major_version(1);
message.mutable_protocol_version()->mutable_version()->set_minor_version(0);
InvalidationP* inv = message.add_invalidation();
inv->mutable_object_id()->set_source(ObjectIdP_Source_INTERNAL);
inv->mutable_object_id()->mutable_name()->set_string_value("ALL");
inv->set_version(1);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
message.SerializeToString(&serialized);
Closure* callback = NULL;
EXPECT_CALL(*listener_, InvalidateAll(_))
.WillOnce(SaveArg<0>(&callback));
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
callback->Run();
delete callback;
}
TEST_F(InvalidationClientImplTest, RejectsProtocolVersion2) {
/* Test plan: initialize the Ticl. Send it a message with the "invalidate
* all" object id and protocol version 2, and check that the app does not get
* an invalidateAll() call, which implies that the Ticl ignored the message
* whose version was too high.
*/
TestInitialization();
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_session_token(session_token_);
message.mutable_protocol_version()->mutable_version()->set_major_version(2);
message.mutable_protocol_version()->mutable_version()->set_minor_version(0);
InvalidationP* inv = message.add_invalidation();
inv->mutable_object_id()->set_source(ObjectIdP_Source_INTERNAL);
inv->mutable_object_id()->mutable_name()->set_string_value("ALL");
inv->set_version(1);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
resources_->RunListenerTasks();
}
TEST_F(InvalidationClientImplTest, Throttling) {
/* Test plan: initialize the Ticl. Send it a message telling it to set its
* heartbeat and polling intervals to 1 ms. Make sure its pings to the app
* don't violate the (default) rate limits.
*/
TestInitialization();
ServerToClientMessage message;
message.mutable_status()->set_code(Status_Code_SUCCESS);
message.set_session_token(session_token_);
message.set_next_heartbeat_interval_ms(1);
message.set_next_poll_interval_ms(1);
message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
string serialized;
message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
// Run for five minutes in 10ms increments, counting the number of times the
// Ticl tells us it has a bundle.
int ping_count = 0;
for (int i = 0; i < 30000; ++i) {
resources_->ModifyTime(TimeDelta::FromMilliseconds(10));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
if (outbound_message_ready_) {
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
outbound_message_ready_ = false;
++ping_count;
}
}
ASSERT_GE(ping_count, 28);
ASSERT_LE(ping_count, 31);
}
TEST_F(InvalidationClientImplTest, Smearing) {
int n_iterations = 500;
double smear_factor = 0.2;
TimeDelta base_delay(TimeDelta::FromSeconds(1));
int num_not_exactly_equal = 0;
TimeDelta abs_smear_sum(TimeDelta::FromSeconds(0));
Random random(0);
for (int i = 0; i < n_iterations; ++i) {
TimeDelta delay = InvalidationClientImpl::SmearDelay(
base_delay, smear_factor, &random);
LOG(INFO) << "delay = " << delay.ToInternalValue();
ASSERT_TRUE((delay >= TimeDelta::FromMilliseconds(800)) &&
(delay <= TimeDelta::FromMilliseconds(1200)));
num_not_exactly_equal += (delay != base_delay);
if (delay < base_delay) {
abs_smear_sum = abs_smear_sum + (base_delay - delay);
} else {
abs_smear_sum = abs_smear_sum + (delay - base_delay);
}
}
// Make sure we actually smeared values. This is a conservative check -- we
// actually expect num_not_exactly_equal == n_iterations.
ASSERT_GT(num_not_exactly_equal, n_iterations / 2);
// Another check on smearing -- we'd actually expect / 2, but be conservative.
ASSERT_TRUE(abs_smear_sum >=
base_delay * static_cast<int64>(smear_factor * n_iterations / 3));
}
TEST_F(InvalidationClientImplTest, MaxSessionRequests) {
// Start up the Ticl, connect a network listener, and let it do its
// initialization.
ticl_->Start("");
ticl_->network_endpoint()->RegisterOutboundListener(network_listener_.get());
resources_->RunReadyTasks();
resources_->RunListenerTasks();
string serialized;
ClientToServerMessage message;
ClientExternalIdP external_id;
for (int i = 0; i < SessionManager::getMaxSessionAttemptsForTest(); ++i) {
// Check that it has a message to send, and pull the message.
ASSERT_TRUE(outbound_message_ready_);
outbound_message_ready_ = false;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
// Check that the message is a proper request for client id assignment.
CheckAssignClientIdRequest(message, &external_id);
// Don't respond.
resources_->ModifyTime(
// Default registration (session request) timeout plus periodic task
// interval.
TimeDelta::FromMinutes(1) + TimeDelta::FromMilliseconds(500));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
}
// Check that it's given up and isn't trying to send anymore.
ASSERT_FALSE(outbound_message_ready_);
// Advance time another hour and check again.
resources_->ModifyTime(
SessionManager::getWakeUpAfterGiveUpIntervalForTest() +
TimeDelta::FromMilliseconds(500));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
// Check that it has a message to send, and pull the message.
ASSERT_TRUE(outbound_message_ready_);
outbound_message_ready_ = false;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
// Check that the message is a proper request for client id assignment.
CheckAssignClientIdRequest(message, &external_id);
}
TEST_F(InvalidationClientImplTest, Persistence) {
// Do a fresh startup.
TestInitialization();
// Make a local copy of the state it persisted.
string state = last_persisted_state_;
// Kill the old resources / Ticl and start a new one.
resources_.reset(new StrictMock<MockSystemResources>());
resources_->ModifyTime(TimeDelta::FromSeconds(1000000));
resources_->StartScheduler();
ClientConfig ticl_config;
ticl_config.smear_factor = 0.0; // Disable smearing for determinism.
ClientType client_type;
client_type.set_type(ClientType_Type_CHROME_SYNC);
string new_state;
StorageCallback* storage_callback = NULL;
Closure* callback = NULL;
// On startup, the Ticl should issue AllRegistrationsLost and
// SessionStatusChanged(true). It should also try to write back immediately
// to claim a new block of sequence numbers.
EXPECT_CALL(*listener_, AllRegistrationsLost(_))
.WillOnce(SaveArg<0>(&callback));
EXPECT_CALL(*listener_, SessionStatusChanged(true));
EXPECT_CALL(*resources_, WriteState(_, _))
.WillOnce(DoAll(SaveArg<0>(&new_state),
SaveArg<1>(&storage_callback)));
ticl_.reset(new InvalidationClientImpl(
resources_.get(), client_type, APP_NAME, CLIENT_INFO, ticl_config,
listener_.get()));
ticl_->Start(state);
ticl_->network_endpoint()->RegisterOutboundListener(network_listener_.get());
resources_->RunReadyTasks();
resources_->RunListenerTasks();
storage_callback->Run(true);
delete storage_callback;
callback->Run();
delete callback;
TiclState parsed_state;
TiclState new_parsed_state;
DeserializeState(state, &parsed_state);
DeserializeState(new_state, &new_parsed_state);
ASSERT_EQ(parsed_state.sequence_number_limit() + ticl_config.seqno_block_size,
new_parsed_state.sequence_number_limit());
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
outbound_message_ready_ = false;
string serialized;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
ClientToServerMessage message;
message.ParseFromString(serialized);
// Ticl should be sending a registration sync request.
ASSERT_EQ(ClientToServerMessage_MessageType_TYPE_REGISTRATION_SYNC,
message.message_type());
ASSERT_FALSE(message.has_action());
// Request to register on some objects.
ObjectIdP object_id3;
object_id3.mutable_name()->set_string_value("timeout-object");
object_id3.set_source(ObjectIdP_Source_CHROME_SYNC);
ObjectIdP object_id4;
object_id4.mutable_name()->set_string_value("spontaneous-reg-object");
object_id4.set_source(ObjectIdP_Source_CHROME_SYNC);
ObjectId oid1;
ObjectId oid2;
ObjectId oid3;
ConvertFromObjectIdProto(object_id1_, &oid1);
ConvertFromObjectIdProto(object_id2_, &oid2);
ConvertFromObjectIdProto(object_id3, &oid3);
ticl_->Register(oid1);
ticl_->Register(oid2);
ticl_->Register(oid3);
// Wait for the next periodic check / message rate limit.
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
outbound_message_ready_ = false;
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
// Ticl should be sending a heartbeat.
ASSERT_EQ(ClientToServerMessage_MessageType_TYPE_OBJECT_CONTROL,
message.message_type());
ASSERT_TRUE(message.has_action());
ASSERT_EQ(ClientToServerMessage_Action_HEARTBEAT, message.action());
// Push some registration responses back.
ServerToClientMessage registration_push_message;
registration_push_message.set_session_token(session_token_);
registration_push_message.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
registration_push_message.set_num_total_registrations(2);
RegistrationUpdateResult* result =
registration_push_message.add_registration_result();
result->mutable_operation()->mutable_object_id()->CopyFrom(object_id1_);
result->mutable_operation()->set_type(RegistrationUpdate_Type_REGISTER);
result->mutable_operation()->set_sequence_number(1);
result->mutable_status()->set_code(Status_Code_SUCCESS);
result = registration_push_message.add_registration_result();
result->mutable_operation()->mutable_object_id()->CopyFrom(object_id4);
result->mutable_operation()->set_type(RegistrationUpdate_Type_REGISTER);
result->mutable_operation()->set_sequence_number(2);
result->mutable_status()->set_code(Status_Code_SUCCESS);
ObjectId oid4;
ConvertFromObjectIdProto(object_id4, &oid4);
EXPECT_CALL(*listener_,
RegistrationStateChanged(
ObjectIdEq(oid4),
RegistrationState_REGISTERED,
_));
registration_push_message.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
// Ticl should be in synced state now, so it should send requests to register
// for the oids for which we didn't push registrations.
resources_->ModifyTime(TimeDelta::FromSeconds(1));
resources_->RunReadyTasks();
resources_->RunListenerTasks();
ASSERT_TRUE(outbound_message_ready_);
ticl_->network_endpoint()->TakeOutboundMessage(&serialized);
message.ParseFromString(serialized);
ASSERT_EQ(ClientToServerMessage_MessageType_TYPE_OBJECT_CONTROL,
message.message_type());
ASSERT_EQ(2, message.register_operation_size());
const RegistrationUpdate& op1 = message.register_operation(0);
const RegistrationUpdate& op2 = message.register_operation(1);
ASSERT_TRUE(ObjectIdPsEqual(op1.object_id(), object_id2_) ||
ObjectIdPsEqual(op1.object_id(), object_id3));
ASSERT_TRUE(ObjectIdPsEqual(op2.object_id(), object_id2_) ||
ObjectIdPsEqual(op2.object_id(), object_id3));
ASSERT_GT(op1.sequence_number(), ticl_config.seqno_block_size);
ASSERT_GT(op2.sequence_number(), ticl_config.seqno_block_size);
// Deliver a failure response for object_id2_ and let object_id3 time out.
ServerToClientMessage response;
response.set_session_token(session_token_);
response.set_message_type(
ServerToClientMessage_MessageType_TYPE_OBJECT_CONTROL);
result = response.add_registration_result();
if (ObjectIdPsEqual(op1.object_id(), object_id2_)) {
result->mutable_operation()->CopyFrom(op1);
} else {
result->mutable_operation()->CopyFrom(op2);
}
result->mutable_status()->set_code(Status_Code_PERMANENT_FAILURE);
// Ticl should inform the listener of the permanent state change for
// object_id2_.
response.SerializeToString(&serialized);
ticl_->network_endpoint()->HandleInboundMessage(serialized);
resources_->RunReadyTasks();
ObjectId object_id2;
ConvertFromObjectIdProto(object_id2_, &object_id2);
EXPECT_CALL(*listener_,
RegistrationStateChanged(
ObjectIdEq(object_id2),
RegistrationState_UNKNOWN,
Property(&UnknownHint::is_transient, false)));
resources_->RunListenerTasks();
// Ticl should inform the listener of the transient state change for
// object_id3.
resources_->ModifyTime(TimeDelta::FromSeconds(80));
resources_->RunReadyTasks();
ObjectId tmp_object_id3;
ConvertFromObjectIdProto(object_id3, &tmp_object_id3);
EXPECT_CALL(*listener_,
RegistrationStateChanged(
ObjectIdEq(tmp_object_id3),
RegistrationState_UNKNOWN,
Property(&UnknownHint::is_transient, true)));
resources_->RunListenerTasks();
}
} // namespace invalidation