| // 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 <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 std::string& 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 + "@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 std::string& account_id) { |
| GCMClient::AccountTokenInfo account_token; |
| account_token.account_id = account_id; |
| account_token.email = account_id + "@gmail.com"; |
| account_token.access_token = account_id + "_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; |
| GCMAccountMapper::AccountMappings::const_iterator expected_iter = |
| expected_mappings.begin(); |
| GCMAccountMapper::AccountMappings::const_iterator 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 std::string& 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 std::string& 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_; |
| std::string 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 std::string& 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) { |
| GetAppHandler(kGCMAccountMapperAppId) |
| ->OnSendAcknowledged(kGCMAccountMapperAppId, message_id); |
| SetLastMessageAction(message_id, SEND_ACKNOWLEDGED); |
| } |
| |
| void CustomFakeGCMDriver::MessageSendError(const std::string& message_id) { |
| GCMClient::SendErrorDetails send_error; |
| send_error.message_id = message_id; |
| send_error.result = GCMClient::TTL_EXCEEDED; |
| GetAppHandler(kGCMAccountMapperAppId) |
| ->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_.clear(); |
| 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: |
| 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() { |
| Restart(); |
| } |
| |
| GCMAccountMapperTest::~GCMAccountMapperTest() { |
| } |
| |
| void GCMAccountMapperTest::Restart() { |
| if (account_mapper_) |
| account_mapper_->ShutdownHandler(); |
| gcm_driver_.RemoveAppHandler(kGCMAccountMapperAppId); |
| account_mapper_.reset(new GCMAccountMapper(&gcm_driver_)); |
| std::unique_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); |
| clock_ = clock.get(); |
| account_mapper_->SetClockForTesting(std::move(clock)); |
| } |
| |
| void GCMAccountMapperTest::Initialize( |
| const GCMAccountMapper::AccountMappings mappings) { |
| mapper()->Initialize(mappings, |
| base::Bind(&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("acc_id2")); |
| 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("acc_id1", |
| AccountMapping::MAPPED, |
| base::Time::Now(), |
| std::string()); |
| AccountMapping account_mapping2 = MakeAccountMapping("acc_id2", |
| 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("acc_id")); |
| mapper()->SetAccountTokens(account_tokens); |
| |
| EXPECT_TRUE(GetAccounts().empty()); |
| |
| account_tokens.clear(); |
| account_tokens.push_back(MakeAccountTokenInfo("acc_id1")); |
| account_tokens.push_back(MakeAccountTokenInfo("acc_id2")); |
| 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("acc_id1", mappings[0].account_id); |
| EXPECT_EQ("acc_id2", 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("acc_id"); |
| 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("acc_id", iter->account_id); |
| EXPECT_EQ("[email protected]", 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("acc_id"); |
| 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("acc_id"); |
| 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("acc_id"); |
| 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("acc_id"); |
| 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("acc_id", |
| 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("acc_id", |
| 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("acc_id", |
| 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("acc_id", |
| 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("acc_id", |
| 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("acc_id", |
| 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("acc_id"); |
| 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( |
| "acc_id", 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("acc_id"); |
| 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::TimeDelta::FromMinutes(30); |
| GCMAccountMapper::AccountMappings stored_mappings; |
| stored_mappings.push_back(MakeAccountMapping( |
| "acc_id_0", AccountMapping::ADDING, half_hour_ago, "acc_id_0_msg")); |
| stored_mappings.push_back(MakeAccountMapping( |
| "acc_id_1", AccountMapping::MAPPED, half_hour_ago, "acc_id_1_msg")); |
| stored_mappings.push_back(MakeAccountMapping( |
| "acc_id_2", 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("acc_id_0")); |
| |
| // Advance a day to make sure existing mappings will be reported. |
| clock()->SetNow(clock()->Now() + base::TimeDelta::FromDays(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::TimeDelta::FromSeconds(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("acc_id_0")); |
| account_tokens.push_back(MakeAccountTokenInfo("acc_id_3")); |
| account_tokens.push_back(MakeAccountTokenInfo("acc_id_4")); |
| |
| // Advance a day to make sure existing mappings will be reported. |
| clock()->SetNow(clock()->Now() + base::TimeDelta::FromDays(1)); |
| mapper()->SetAccountTokens(account_tokens); |
| |
| // Mapping from acc_id_0 still in position 0 |
| expected_mappings.push_back(MakeAccountMapping( |
| "acc_id_3", AccountMapping::NEW, base::Time(), std::string())); |
| expected_mappings.push_back(MakeAccountMapping( |
| "acc_id_4", AccountMapping::NEW, base::Time(), std::string())); |
| |
| VerifyMappings(expected_mappings, GetAccounts(), "Step 4, Two new accounts"); |
| |
| clock()->SetNow(clock()->Now() + base::TimeDelta::FromSeconds(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::TimeDelta::FromSeconds(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()); |
| MessageData::const_iterator 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 |