diff options
Diffstat (limited to 'chromium/components/gcm_driver/gcm_account_mapper_unittest.cc')
-rw-r--r-- | chromium/components/gcm_driver/gcm_account_mapper_unittest.cc | 955 |
1 files changed, 955 insertions, 0 deletions
diff --git a/chromium/components/gcm_driver/gcm_account_mapper_unittest.cc b/chromium/components/gcm_driver/gcm_account_mapper_unittest.cc new file mode 100644 index 00000000000..286d7e41915 --- /dev/null +++ b/chromium/components/gcm_driver/gcm_account_mapper_unittest.cc @@ -0,0 +1,955 @@ +// 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/gcm_driver/gcm_account_mapper.h" + +#include <memory> +#include <utility> + +#include "base/bind.h" +#include "base/test/simple_test_clock.h" +#include "base/time/time.h" +#include "components/gcm_driver/fake_gcm_driver.h" +#include "google_apis/gcm/engine/account_mapping.h" +#include "google_apis/gcm/engine/gcm_store.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gcm { + +namespace { + +const char kGCMAccountMapperSenderId[] = "745476177629"; +const char kGCMAccountMapperSendTo[] = "google.com"; +const char kRegistrationId[] = "reg_id"; +const char kEmbeddedAppIdKey[] = "gcmb"; +const char kTestAppId[] = "test_app_id"; +const char kTestDataKey[] = "data_key"; +const char kTestDataValue[] = "data_value"; +const char kTestCollapseKey[] = "test_collapse_key"; +const char kTestSenderId[] = "test_sender_id"; + +AccountMapping MakeAccountMapping(const CoreAccountId& account_id, + AccountMapping::MappingStatus status, + const base::Time& status_change_timestamp, + const std::string& last_message_id) { + AccountMapping account_mapping; + account_mapping.account_id = account_id; + account_mapping.email = account_id.ToString() + "@gmail.com"; + // account_mapping.access_token intentionally left empty. + account_mapping.status = status; + account_mapping.status_change_timestamp = status_change_timestamp; + account_mapping.last_message_id = last_message_id; + return account_mapping; +} + +GCMClient::AccountTokenInfo MakeAccountTokenInfo( + const CoreAccountId& account_id) { + GCMClient::AccountTokenInfo account_token; + account_token.account_id = account_id; + account_token.email = account_id.ToString() + "@gmail.com"; + account_token.access_token = account_id.ToString() + "_token"; + return account_token; +} + +void VerifyMappings(const GCMAccountMapper::AccountMappings& expected_mappings, + const GCMAccountMapper::AccountMappings& actual_mappings, + const std::string& verification_info) { + EXPECT_EQ(expected_mappings.size(), actual_mappings.size()) + << "Verification Info: " << verification_info; + auto expected_iter = expected_mappings.begin(); + auto actual_iter = actual_mappings.begin(); + for (; expected_iter != expected_mappings.end() && + actual_iter != actual_mappings.end(); + ++expected_iter, ++actual_iter) { + EXPECT_EQ(expected_iter->email, actual_iter->email) + << "Verification Info: " << verification_info + << "; Account ID of expected: " << expected_iter->account_id; + EXPECT_EQ(expected_iter->account_id, actual_iter->account_id) + << "Verification Info: " << verification_info; + EXPECT_EQ(expected_iter->status, actual_iter->status) + << "Verification Info: " << verification_info + << "; Account ID of expected: " << expected_iter->account_id; + EXPECT_EQ(expected_iter->status_change_timestamp, + actual_iter->status_change_timestamp) + << "Verification Info: " << verification_info + << "; Account ID of expected: " << expected_iter->account_id; + } +} + +class CustomFakeGCMDriver : public FakeGCMDriver { + public: + enum LastMessageAction { + NONE, + SEND_STARTED, + SEND_FINISHED, + SEND_ACKNOWLEDGED + }; + + CustomFakeGCMDriver(); + ~CustomFakeGCMDriver() override; + + // GCMDriver implementation: + void UpdateAccountMapping(const AccountMapping& account_mapping) override; + void RemoveAccountMapping(const CoreAccountId& account_id) override; + void RegisterImpl(const std::string& app_id, + const std::vector<std::string>& sender_ids) override; + + void CompleteRegister(const std::string& registration_id, + GCMClient::Result result); + void CompleteSend(const std::string& message_id, GCMClient::Result result); + void AcknowledgeSend(const std::string& message_id); + void MessageSendError(const std::string& message_id); + + void CompleteSendAllMessages(); + void AcknowledgeSendAllMessages(); + void SetLastMessageAction(const std::string& message_id, + LastMessageAction action); + void Clear(); + + const AccountMapping& last_account_mapping() const { + return account_mapping_; + } + const std::string& last_message_id() const { return last_message_id_; } + const CoreAccountId& last_removed_account_id() const { + return last_removed_account_id_; + } + LastMessageAction last_action() const { return last_action_; } + bool registration_id_requested() const { return registration_id_requested_; } + + protected: + void SendImpl(const std::string& app_id, + const std::string& receiver_id, + const OutgoingMessage& message) override; + + private: + AccountMapping account_mapping_; + std::string last_message_id_; + CoreAccountId last_removed_account_id_; + LastMessageAction last_action_; + std::map<std::string, LastMessageAction> all_messages_; + bool registration_id_requested_; +}; + +CustomFakeGCMDriver::CustomFakeGCMDriver() + : last_action_(NONE), registration_id_requested_(false) { +} + +CustomFakeGCMDriver::~CustomFakeGCMDriver() { +} + +void CustomFakeGCMDriver::UpdateAccountMapping( + const AccountMapping& account_mapping) { + account_mapping_.email = account_mapping.email; + account_mapping_.account_id = account_mapping.account_id; + account_mapping_.access_token = account_mapping.access_token; + account_mapping_.status = account_mapping.status; + account_mapping_.status_change_timestamp = + account_mapping.status_change_timestamp; + account_mapping_.last_message_id = account_mapping.last_message_id; +} + +void CustomFakeGCMDriver::RemoveAccountMapping( + const CoreAccountId& account_id) { + last_removed_account_id_ = account_id; +} + +void CustomFakeGCMDriver::RegisterImpl( + const std::string& app_id, + const std::vector<std::string>& sender_ids) { + DCHECK_EQ(kGCMAccountMapperAppId, app_id); + DCHECK_EQ(1u, sender_ids.size()); + DCHECK_EQ(kGCMAccountMapperSenderId, sender_ids[0]); + registration_id_requested_ = true; +} + +void CustomFakeGCMDriver::CompleteRegister(const std::string& registration_id, + GCMClient::Result result) { + RegisterFinished(kGCMAccountMapperAppId, registration_id, result); +} + +void CustomFakeGCMDriver::CompleteSend(const std::string& message_id, + GCMClient::Result result) { + SendFinished(kGCMAccountMapperAppId, message_id, result); + SetLastMessageAction(message_id, SEND_FINISHED); +} + +void CustomFakeGCMDriver::AcknowledgeSend(const std::string& message_id) { + GCMAppHandler* handler = GetAppHandler(kGCMAccountMapperAppId); + if (handler) + handler->OnSendAcknowledged(kGCMAccountMapperAppId, message_id); + SetLastMessageAction(message_id, SEND_ACKNOWLEDGED); +} + +void CustomFakeGCMDriver::MessageSendError(const std::string& message_id) { + GCMAppHandler* handler = GetAppHandler(kGCMAccountMapperAppId); + if (!handler) + return; + + GCMClient::SendErrorDetails send_error; + send_error.message_id = message_id; + send_error.result = GCMClient::TTL_EXCEEDED; + + handler->OnSendError(kGCMAccountMapperAppId, send_error); +} + +void CustomFakeGCMDriver::SendImpl(const std::string& app_id, + const std::string& receiver_id, + const OutgoingMessage& message) { + DCHECK_EQ(kGCMAccountMapperAppId, app_id); + DCHECK_EQ(kGCMAccountMapperSendTo, receiver_id); + + SetLastMessageAction(message.id, SEND_STARTED); +} + +void CustomFakeGCMDriver::CompleteSendAllMessages() { + for (std::map<std::string, LastMessageAction>::const_iterator iter = + all_messages_.begin(); + iter != all_messages_.end(); + ++iter) { + if (iter->second == SEND_STARTED) + CompleteSend(iter->first, GCMClient::SUCCESS); + } +} + +void CustomFakeGCMDriver::AcknowledgeSendAllMessages() { + for (std::map<std::string, LastMessageAction>::const_iterator iter = + all_messages_.begin(); + iter != all_messages_.end(); + ++iter) { + if (iter->second == SEND_FINISHED) + AcknowledgeSend(iter->first); + } +} + +void CustomFakeGCMDriver::Clear() { + account_mapping_ = AccountMapping(); + last_message_id_.clear(); + last_removed_account_id_ = CoreAccountId(); + last_action_ = NONE; + registration_id_requested_ = false; +} + +void CustomFakeGCMDriver::SetLastMessageAction(const std::string& message_id, + LastMessageAction action) { + last_action_ = action; + last_message_id_ = message_id; + all_messages_[message_id] = action; +} + +} // namespace + +class GCMAccountMapperTest : public testing::Test { + public: + const CoreAccountId kAccountId; + const CoreAccountId kAccountId1; + const CoreAccountId kAccountId2; + const CoreAccountId kAccountId3; + const CoreAccountId kAccountId4; + + GCMAccountMapperTest(); + ~GCMAccountMapperTest() override; + + void Restart(); + + void Initialize(const GCMAccountMapper::AccountMappings mappings); + const GCMAccountMapper::AccountMappings& GetAccounts() const { + return account_mapper_->accounts_; + } + void MessageReceived(const std::string& app_id, + const IncomingMessage& message); + + GCMAccountMapper* mapper() { return account_mapper_.get(); } + + CustomFakeGCMDriver& gcm_driver() { return gcm_driver_; } + + base::SimpleTestClock* clock() { return &clock_; } + const std::string& last_received_app_id() const { + return last_received_app_id_; + } + const IncomingMessage& last_received_message() const { + return last_received_message_; + } + + private: + CustomFakeGCMDriver gcm_driver_; + std::unique_ptr<GCMAccountMapper> account_mapper_; + base::SimpleTestClock clock_; + std::string last_received_app_id_; + IncomingMessage last_received_message_; +}; + +GCMAccountMapperTest::GCMAccountMapperTest() + : kAccountId("acc_id"), + kAccountId1("acc_id1"), + kAccountId2("acc_id2"), + kAccountId3("acc_id3"), + kAccountId4("acc_id4") { + Restart(); +} + +GCMAccountMapperTest::~GCMAccountMapperTest() { +} + +void GCMAccountMapperTest::Restart() { + if (account_mapper_) + account_mapper_->ShutdownHandler(); + gcm_driver_.RemoveAppHandler(kGCMAccountMapperAppId); + account_mapper_ = std::make_unique<GCMAccountMapper>(&gcm_driver_); + account_mapper_->SetClockForTesting(&clock_); +} + +void GCMAccountMapperTest::Initialize( + const GCMAccountMapper::AccountMappings mappings) { + mapper()->Initialize( + mappings, base::BindRepeating(&GCMAccountMapperTest::MessageReceived, + base::Unretained(this))); +} + +void GCMAccountMapperTest::MessageReceived(const std::string& app_id, + const IncomingMessage& message) { + last_received_app_id_ = app_id; + last_received_message_ = message; +} + +// Tests the initialization of account mappings (from the store) when empty. +// It also checks that initialization triggers registration ID request. +TEST_F(GCMAccountMapperTest, InitializeAccountMappingsEmpty) { + EXPECT_FALSE(gcm_driver().registration_id_requested()); + Initialize(GCMAccountMapper::AccountMappings()); + EXPECT_TRUE(GetAccounts().empty()); + EXPECT_TRUE(gcm_driver().registration_id_requested()); +} + +// Tests that registration is retried, when new tokens are delivered and in no +// other circumstances. +TEST_F(GCMAccountMapperTest, RegistrationRetryUponFailure) { + Initialize(GCMAccountMapper::AccountMappings()); + EXPECT_TRUE(gcm_driver().registration_id_requested()); + gcm_driver().Clear(); + + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::UNKNOWN_ERROR); + EXPECT_FALSE(gcm_driver().registration_id_requested()); + gcm_driver().Clear(); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + account_tokens.push_back(MakeAccountTokenInfo(kAccountId2)); + mapper()->SetAccountTokens(account_tokens); + EXPECT_TRUE(gcm_driver().registration_id_requested()); + gcm_driver().Clear(); + + gcm_driver().CompleteRegister(kRegistrationId, + GCMClient::ASYNC_OPERATION_PENDING); + EXPECT_FALSE(gcm_driver().registration_id_requested()); +} + +// Tests the initialization of account mappings (from the store). +TEST_F(GCMAccountMapperTest, InitializeAccountMappings) { + GCMAccountMapper::AccountMappings account_mappings; + AccountMapping account_mapping1 = MakeAccountMapping( + kAccountId1, AccountMapping::MAPPED, base::Time::Now(), std::string()); + AccountMapping account_mapping2 = MakeAccountMapping( + kAccountId2, AccountMapping::ADDING, base::Time::Now(), "add_message_1"); + account_mappings.push_back(account_mapping1); + account_mappings.push_back(account_mapping2); + + Initialize(account_mappings); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + EXPECT_EQ(2UL, mappings.size()); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + + EXPECT_EQ(account_mapping1.account_id, iter->account_id); + EXPECT_EQ(account_mapping1.email, iter->email); + EXPECT_TRUE(account_mapping1.access_token.empty()); + EXPECT_EQ(account_mapping1.status, iter->status); + EXPECT_EQ(account_mapping1.status_change_timestamp, + iter->status_change_timestamp); + EXPECT_TRUE(account_mapping1.last_message_id.empty()); + + ++iter; + EXPECT_EQ(account_mapping2.account_id, iter->account_id); + EXPECT_EQ(account_mapping2.email, iter->email); + EXPECT_TRUE(account_mapping2.access_token.empty()); + EXPECT_EQ(account_mapping2.status, iter->status); + EXPECT_EQ(account_mapping2.status_change_timestamp, + iter->status_change_timestamp); + EXPECT_EQ(account_mapping2.last_message_id, iter->last_message_id); +} + +// Tests that account tokens are not processed until registration ID is +// available. +TEST_F(GCMAccountMapperTest, SetAccountTokensOnlyWorksWithRegisterationId) { + // Start with empty list. + Initialize(GCMAccountMapper::AccountMappings()); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + account_tokens.push_back(MakeAccountTokenInfo(kAccountId)); + mapper()->SetAccountTokens(account_tokens); + + EXPECT_TRUE(GetAccounts().empty()); + + account_tokens.clear(); + account_tokens.push_back(MakeAccountTokenInfo(kAccountId1)); + account_tokens.push_back(MakeAccountTokenInfo(kAccountId2)); + mapper()->SetAccountTokens(account_tokens); + + EXPECT_TRUE(GetAccounts().empty()); + + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + EXPECT_EQ(2UL, mappings.size()); + EXPECT_EQ(kAccountId1, mappings[0].account_id); + EXPECT_EQ(kAccountId2, mappings[1].account_id); +} + +// Tests the part where a new account is added with a token, to the point when +// GCM message is sent. +TEST_F(GCMAccountMapperTest, AddMappingToMessageSent) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + EXPECT_EQ(1UL, mappings.size()); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(kAccountId, iter->account_id); + EXPECT_EQ("acc_id@gmail.com", iter->email); + EXPECT_EQ("acc_id_token", iter->access_token); + EXPECT_EQ(AccountMapping::NEW, iter->status); + EXPECT_EQ(base::Time(), iter->status_change_timestamp); + + EXPECT_TRUE(!gcm_driver().last_message_id().empty()); +} + +// Tests the part where GCM message is successfully queued. +TEST_F(GCMAccountMapperTest, AddMappingMessageQueued) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + + EXPECT_EQ(account_token.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(account_token.account_id, + gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(account_token.access_token, + gcm_driver().last_account_mapping().access_token); + EXPECT_EQ(AccountMapping::ADDING, gcm_driver().last_account_mapping().status); + EXPECT_EQ(clock()->Now(), + gcm_driver().last_account_mapping().status_change_timestamp); + EXPECT_EQ(gcm_driver().last_message_id(), + gcm_driver().last_account_mapping().last_message_id); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(account_token.email, iter->email); + EXPECT_EQ(account_token.account_id, iter->account_id); + EXPECT_EQ(account_token.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::ADDING, iter->status); + EXPECT_EQ(clock()->Now(), iter->status_change_timestamp); + EXPECT_EQ(gcm_driver().last_message_id(), iter->last_message_id); +} + +// Tests status change from ADDING to MAPPED (Message is acknowledged). +TEST_F(GCMAccountMapperTest, AddMappingMessageAcknowledged) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + clock()->SetNow(base::Time::Now()); + gcm_driver().AcknowledgeSend(gcm_driver().last_message_id()); + + EXPECT_EQ(account_token.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(account_token.account_id, + gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(account_token.access_token, + gcm_driver().last_account_mapping().access_token); + EXPECT_EQ(AccountMapping::MAPPED, gcm_driver().last_account_mapping().status); + EXPECT_EQ(clock()->Now(), + gcm_driver().last_account_mapping().status_change_timestamp); + EXPECT_TRUE(gcm_driver().last_account_mapping().last_message_id.empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(account_token.email, iter->email); + EXPECT_EQ(account_token.account_id, iter->account_id); + EXPECT_EQ(account_token.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::MAPPED, iter->status); + EXPECT_EQ(clock()->Now(), iter->status_change_timestamp); + EXPECT_TRUE(iter->last_message_id.empty()); +} + +// Tests status change form ADDING to MAPPED (When message was acknowledged, +// after Chrome was restarted). +TEST_F(GCMAccountMapperTest, AddMappingMessageAckedAfterRestart) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + + Restart(); + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(gcm_driver().last_account_mapping()); + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + + clock()->SetNow(base::Time::Now()); + gcm_driver().AcknowledgeSend(gcm_driver().last_message_id()); + + EXPECT_EQ(account_token.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(account_token.account_id, + gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(account_token.access_token, + gcm_driver().last_account_mapping().access_token); + EXPECT_EQ(AccountMapping::MAPPED, gcm_driver().last_account_mapping().status); + EXPECT_EQ(clock()->Now(), + gcm_driver().last_account_mapping().status_change_timestamp); + EXPECT_TRUE(gcm_driver().last_account_mapping().last_message_id.empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(account_token.email, iter->email); + EXPECT_EQ(account_token.account_id, iter->account_id); + EXPECT_EQ(account_token.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::MAPPED, iter->status); + EXPECT_EQ(clock()->Now(), iter->status_change_timestamp); + EXPECT_TRUE(iter->last_message_id.empty()); +} + +// Tests a case when ADD message times out for a new account. +TEST_F(GCMAccountMapperTest, AddMappingMessageSendErrorForNewAccount) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + + clock()->SetNow(base::Time::Now()); + std::string old_message_id = gcm_driver().last_message_id(); + gcm_driver().MessageSendError(old_message_id); + + // No new message is sent because of the send error, as the token is stale. + // Because the account was new, the entry should be deleted. + EXPECT_EQ(old_message_id, gcm_driver().last_message_id()); + EXPECT_EQ(account_token.account_id, gcm_driver().last_removed_account_id()); + EXPECT_TRUE(GetAccounts().empty()); +} + +/// Tests a case when ADD message times out for a MAPPED account. +TEST_F(GCMAccountMapperTest, AddMappingMessageSendErrorForMappedAccount) { + // Start with one account that is mapped. + base::Time status_change_timestamp = base::Time::Now(); + AccountMapping mapping = + MakeAccountMapping(kAccountId, AccountMapping::MAPPED, + status_change_timestamp, "add_message_id"); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + clock()->SetNow(base::Time::Now()); + gcm_driver().MessageSendError("add_message_id"); + + // No new message is sent because of the send error, as the token is stale. + // Because the account was new, the entry should be deleted. + EXPECT_TRUE(gcm_driver().last_message_id().empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(mapping.email, iter->email); + EXPECT_EQ(mapping.account_id, iter->account_id); + EXPECT_EQ(mapping.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::MAPPED, iter->status); + EXPECT_EQ(status_change_timestamp, iter->status_change_timestamp); + EXPECT_TRUE(iter->last_message_id.empty()); +} + +// Tests that a missing token for an account will trigger removing of that +// account. This test goes only until the message is passed to GCM. +TEST_F(GCMAccountMapperTest, RemoveMappingToMessageSent) { + // Start with one account that is mapped. + AccountMapping mapping = MakeAccountMapping( + kAccountId, AccountMapping::MAPPED, base::Time::Now(), std::string()); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + clock()->SetNow(base::Time::Now()); + + mapper()->SetAccountTokens(std::vector<GCMClient::AccountTokenInfo>()); + + EXPECT_EQ(mapping.account_id, gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(mapping.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(AccountMapping::REMOVING, + gcm_driver().last_account_mapping().status); + EXPECT_EQ(clock()->Now(), + gcm_driver().last_account_mapping().status_change_timestamp); + EXPECT_TRUE(gcm_driver().last_account_mapping().last_message_id.empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(mapping.email, iter->email); + EXPECT_EQ(mapping.account_id, iter->account_id); + EXPECT_EQ(mapping.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::REMOVING, iter->status); + EXPECT_EQ(clock()->Now(), iter->status_change_timestamp); + EXPECT_TRUE(iter->last_message_id.empty()); +} + +// Tests that a missing token for an account will trigger removing of that +// account. This test goes until the message is queued by GCM. +TEST_F(GCMAccountMapperTest, RemoveMappingMessageQueued) { + // Start with one account that is mapped. + AccountMapping mapping = MakeAccountMapping( + kAccountId, AccountMapping::MAPPED, base::Time::Now(), std::string()); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + clock()->SetNow(base::Time::Now()); + base::Time status_change_timestamp = clock()->Now(); + + mapper()->SetAccountTokens(std::vector<GCMClient::AccountTokenInfo>()); + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + + EXPECT_EQ(mapping.account_id, gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(mapping.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(AccountMapping::REMOVING, + gcm_driver().last_account_mapping().status); + EXPECT_EQ(status_change_timestamp, + gcm_driver().last_account_mapping().status_change_timestamp); + EXPECT_TRUE(!gcm_driver().last_account_mapping().last_message_id.empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(mapping.email, iter->email); + EXPECT_EQ(mapping.account_id, iter->account_id); + EXPECT_EQ(mapping.access_token, iter->access_token); + EXPECT_EQ(AccountMapping::REMOVING, iter->status); + EXPECT_EQ(status_change_timestamp, iter->status_change_timestamp); + EXPECT_EQ(gcm_driver().last_account_mapping().last_message_id, + iter->last_message_id); +} + +// Tests that a missing token for an account will trigger removing of that +// account. This test goes until the message is acknowledged by GCM. +// This is a complete success scenario for account removal, and it end with +// account mapping being completely gone. +TEST_F(GCMAccountMapperTest, RemoveMappingMessageAcknowledged) { + // Start with one account that is mapped. + AccountMapping mapping = MakeAccountMapping( + kAccountId, AccountMapping::MAPPED, base::Time::Now(), std::string()); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + clock()->SetNow(base::Time::Now()); + + mapper()->SetAccountTokens(std::vector<GCMClient::AccountTokenInfo>()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + gcm_driver().AcknowledgeSend(gcm_driver().last_message_id()); + + EXPECT_EQ(mapping.account_id, gcm_driver().last_removed_account_id()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + EXPECT_TRUE(mappings.empty()); +} + +// Tests that account removing proceeds, when a removing message is acked after +// Chrome was restarted. +TEST_F(GCMAccountMapperTest, RemoveMappingMessageAckedAfterRestart) { + // Start with one account that is mapped. + AccountMapping mapping = + MakeAccountMapping(kAccountId, AccountMapping::REMOVING, + base::Time::Now(), "remove_message_id"); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + + gcm_driver().AcknowledgeSend("remove_message_id"); + + EXPECT_EQ(mapping.account_id, gcm_driver().last_removed_account_id()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + EXPECT_TRUE(mappings.empty()); +} + +// Tests that account removing proceeds, when a removing message is acked after +// Chrome was restarted. +TEST_F(GCMAccountMapperTest, RemoveMappingMessageSendError) { + // Start with one account that is mapped. + base::Time status_change_timestamp = base::Time::Now(); + AccountMapping mapping = + MakeAccountMapping(kAccountId, AccountMapping::REMOVING, + status_change_timestamp, "remove_message_id"); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + + clock()->SetNow(base::Time::Now()); + gcm_driver().MessageSendError("remove_message_id"); + + EXPECT_TRUE(gcm_driver().last_removed_account_id().empty()); + + EXPECT_EQ(mapping.account_id, gcm_driver().last_account_mapping().account_id); + EXPECT_EQ(mapping.email, gcm_driver().last_account_mapping().email); + EXPECT_EQ(AccountMapping::REMOVING, + gcm_driver().last_account_mapping().status); + EXPECT_EQ(status_change_timestamp, + gcm_driver().last_account_mapping().status_change_timestamp); + // Message is not persisted, until send is completed. + EXPECT_TRUE(gcm_driver().last_account_mapping().last_message_id.empty()); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(mapping.email, iter->email); + EXPECT_EQ(mapping.account_id, iter->account_id); + EXPECT_TRUE(iter->access_token.empty()); + EXPECT_EQ(AccountMapping::REMOVING, iter->status); + EXPECT_EQ(status_change_timestamp, iter->status_change_timestamp); + EXPECT_TRUE(iter->last_message_id.empty()); +} + +// Tests that, if a new token arrives when the adding message is in progress +// no new message is sent and account mapper still waits for the first one to +// complete. +TEST_F(GCMAccountMapperTest, TokenIsRefreshedWhenAdding) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + clock()->SetNow(base::Time::Now()); + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + DCHECK_EQ(CustomFakeGCMDriver::SEND_STARTED, gcm_driver().last_action()); + + clock()->SetNow(base::Time::Now()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + DCHECK_EQ(CustomFakeGCMDriver::SEND_FINISHED, gcm_driver().last_action()); + + // Providing another token and clearing status. + gcm_driver().Clear(); + mapper()->SetAccountTokens(account_tokens); + DCHECK_EQ(CustomFakeGCMDriver::NONE, gcm_driver().last_action()); +} + +// Tests that, if a new token arrives when a removing message is in progress +// a new adding message is sent and while account mapping status is changed to +// mapped. If the original Removing message arrives it is discarded. +TEST_F(GCMAccountMapperTest, TokenIsRefreshedWhenRemoving) { + // Start with one account that is mapped. + AccountMapping mapping = MakeAccountMapping( + kAccountId, AccountMapping::MAPPED, base::Time::Now(), std::string()); + + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(mapping); + Initialize(stored_mappings); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + clock()->SetNow(base::Time::Now()); + + // Remove the token to trigger a remove message to be sent + mapper()->SetAccountTokens(std::vector<GCMClient::AccountTokenInfo>()); + EXPECT_EQ(CustomFakeGCMDriver::SEND_STARTED, gcm_driver().last_action()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + EXPECT_EQ(CustomFakeGCMDriver::SEND_FINISHED, gcm_driver().last_action()); + + std::string remove_message_id = gcm_driver().last_message_id(); + gcm_driver().Clear(); + + // The account mapping for acc_id is now in status REMOVING. + // Adding the token for that account. + clock()->SetNow(base::Time::Now()); + std::vector<GCMClient::AccountTokenInfo> account_tokens; + GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo(kAccountId); + account_tokens.push_back(account_token); + mapper()->SetAccountTokens(account_tokens); + DCHECK_EQ(CustomFakeGCMDriver::SEND_STARTED, gcm_driver().last_action()); + gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS); + EXPECT_EQ(CustomFakeGCMDriver::SEND_FINISHED, gcm_driver().last_action()); + + std::string add_message_id = gcm_driver().last_message_id(); + + // A remove message confirmation arrives now, but should be ignored. + gcm_driver().AcknowledgeSend(remove_message_id); + + GCMAccountMapper::AccountMappings mappings = GetAccounts(); + GCMAccountMapper::AccountMappings::const_iterator iter = mappings.begin(); + EXPECT_EQ(mapping.email, iter->email); + EXPECT_EQ(mapping.account_id, iter->account_id); + EXPECT_FALSE(iter->access_token.empty()); + EXPECT_EQ(AccountMapping::MAPPED, iter->status); + // Status change timestamp is set to very long time ago, to make sure the next + // round of mapping picks it up. + EXPECT_EQ(base::Time(), iter->status_change_timestamp); + EXPECT_EQ(add_message_id, iter->last_message_id); +} + +// Tests adding/removing works for multiple accounts, after a restart and when +// tokens are periodically delierverd. +TEST_F(GCMAccountMapperTest, MultipleAccountMappings) { + clock()->SetNow(base::Time::Now()); + base::Time half_hour_ago = clock()->Now() - base::Minutes(30); + GCMAccountMapper::AccountMappings stored_mappings; + stored_mappings.push_back(MakeAccountMapping( + kAccountId, AccountMapping::ADDING, half_hour_ago, "acc_id_msg")); + stored_mappings.push_back(MakeAccountMapping( + kAccountId1, AccountMapping::MAPPED, half_hour_ago, "acc_id_1_msg")); + stored_mappings.push_back(MakeAccountMapping( + kAccountId2, AccountMapping::REMOVING, half_hour_ago, "acc_id_2_msg")); + + Initialize(stored_mappings); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + gcm_driver().CompleteRegister(kRegistrationId, GCMClient::SUCCESS); + + GCMAccountMapper::AccountMappings expected_mappings(stored_mappings); + + // Finish messages after a restart. + clock()->SetNow(base::Time::Now()); + gcm_driver().AcknowledgeSend(expected_mappings[0].last_message_id); + expected_mappings[0].status_change_timestamp = clock()->Now(); + expected_mappings[0].status = AccountMapping::MAPPED; + expected_mappings[0].last_message_id.clear(); + + clock()->SetNow(base::Time::Now()); + gcm_driver().AcknowledgeSend(expected_mappings[1].last_message_id); + expected_mappings[1].status_change_timestamp = clock()->Now(); + expected_mappings[1].status = AccountMapping::MAPPED; + expected_mappings[1].last_message_id.clear(); + + // Upon success last element is removed. + clock()->SetNow(base::Time::Now()); + gcm_driver().AcknowledgeSend(expected_mappings[2].last_message_id); + expected_mappings.pop_back(); + + VerifyMappings(expected_mappings, GetAccounts(), "Step 1, After restart"); + + // One of accounts gets removed. + std::vector<GCMClient::AccountTokenInfo> account_tokens; + account_tokens.push_back(MakeAccountTokenInfo(kAccountId)); + + // Advance a day to make sure existing mappings will be reported. + clock()->SetNow(clock()->Now() + base::Days(1)); + mapper()->SetAccountTokens(account_tokens); + + expected_mappings[0].status = AccountMapping::MAPPED; + expected_mappings[1].status = AccountMapping::REMOVING; + expected_mappings[1].status_change_timestamp = clock()->Now(); + + gcm_driver().CompleteSendAllMessages(); + + VerifyMappings( + expected_mappings, GetAccounts(), "Step 2, One account is being removed"); + + clock()->SetNow(clock()->Now() + base::Seconds(5)); + gcm_driver().AcknowledgeSendAllMessages(); + + expected_mappings[0].status_change_timestamp = clock()->Now(); + expected_mappings.pop_back(); + + VerifyMappings( + expected_mappings, GetAccounts(), "Step 3, Removing completed"); + + account_tokens.clear(); + account_tokens.push_back(MakeAccountTokenInfo(kAccountId)); + account_tokens.push_back(MakeAccountTokenInfo(kAccountId3)); + account_tokens.push_back(MakeAccountTokenInfo(kAccountId4)); + + // Advance a day to make sure existing mappings will be reported. + clock()->SetNow(clock()->Now() + base::Days(1)); + mapper()->SetAccountTokens(account_tokens); + + // Mapping from acc_id_0 still in position 0 + expected_mappings.push_back(MakeAccountMapping( + kAccountId3, AccountMapping::NEW, base::Time(), std::string())); + expected_mappings.push_back(MakeAccountMapping( + kAccountId4, AccountMapping::NEW, base::Time(), std::string())); + + VerifyMappings(expected_mappings, GetAccounts(), "Step 4, Two new accounts"); + + clock()->SetNow(clock()->Now() + base::Seconds(1)); + gcm_driver().CompleteSendAllMessages(); + + expected_mappings[1].status = AccountMapping::ADDING; + expected_mappings[1].status_change_timestamp = clock()->Now(); + expected_mappings[2].status = AccountMapping::ADDING; + expected_mappings[2].status_change_timestamp = clock()->Now(); + + VerifyMappings( + expected_mappings, GetAccounts(), "Step 5, Two accounts being added"); + + clock()->SetNow(clock()->Now() + base::Seconds(5)); + gcm_driver().AcknowledgeSendAllMessages(); + + expected_mappings[0].status_change_timestamp = clock()->Now(); + expected_mappings[1].status_change_timestamp = clock()->Now(); + expected_mappings[1].status = AccountMapping::MAPPED; + expected_mappings[2].status_change_timestamp = clock()->Now(); + expected_mappings[2].status = AccountMapping::MAPPED; + + VerifyMappings( + expected_mappings, GetAccounts(), "Step 6, Three mapped accounts"); +} + +TEST_F(GCMAccountMapperTest, DispatchMessageSentToGaiaID) { + Initialize(GCMAccountMapper::AccountMappings()); + gcm_driver().AddAppHandler(kGCMAccountMapperAppId, mapper()); + IncomingMessage message; + message.data[kEmbeddedAppIdKey] = kTestAppId; + message.data[kTestDataKey] = kTestDataValue; + message.collapse_key = kTestCollapseKey; + message.sender_id = kTestSenderId; + mapper()->OnMessage(kGCMAccountMapperAppId, message); + + EXPECT_EQ(kTestAppId, last_received_app_id()); + EXPECT_EQ(1UL, last_received_message().data.size()); + auto it = last_received_message().data.find(kTestDataKey); + EXPECT_TRUE(it != last_received_message().data.end()); + EXPECT_EQ(kTestDataValue, it->second); + EXPECT_EQ(kTestCollapseKey, last_received_message().collapse_key); + EXPECT_EQ(kTestSenderId, last_received_message().sender_id); +} + +} // namespace gcm |