Commit c1047318 authored by fgorski's avatar fgorski Committed by Commit bot

[GCM] Adding GCMAccountMapper to link signed in profile to accounts.

* Addes GCMAccountMapper with tests for adding and removing accoounts.

BUG=374969

Review URL: https://codereview.chromium.org/491443004

Cr-Commit-Position: refs/heads/master@{#293308}
parent 6017d60f
...@@ -119,6 +119,7 @@ ...@@ -119,6 +119,7 @@
'feedback/feedback_common_unittest.cc', 'feedback/feedback_common_unittest.cc',
'feedback/feedback_data_unittest.cc', 'feedback/feedback_data_unittest.cc',
'feedback/feedback_uploader_unittest.cc', 'feedback/feedback_uploader_unittest.cc',
'gcm_driver/gcm_account_mapper_unittest.cc',
'gcm_driver/gcm_client_impl_unittest.cc', 'gcm_driver/gcm_client_impl_unittest.cc',
'gcm_driver/gcm_driver_desktop_unittest.cc', 'gcm_driver/gcm_driver_desktop_unittest.cc',
'gcm_driver/gcm_stats_recorder_impl_unittest.cc', 'gcm_driver/gcm_stats_recorder_impl_unittest.cc',
...@@ -577,6 +578,7 @@ ...@@ -577,6 +578,7 @@
}], }],
['OS == "android"', { ['OS == "android"', {
'sources!': [ 'sources!': [
'gcm_driver/gcm_account_mapper_unittest.cc',
'gcm_driver/gcm_client_impl_unittest.cc', 'gcm_driver/gcm_client_impl_unittest.cc',
'gcm_driver/gcm_driver_desktop_unittest.cc', 'gcm_driver/gcm_driver_desktop_unittest.cc',
'feedback/feedback_common_unittest.cc', 'feedback/feedback_common_unittest.cc',
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
'gcm_driver/android/component_jni_registrar.h', 'gcm_driver/android/component_jni_registrar.h',
'gcm_driver/default_gcm_app_handler.cc', 'gcm_driver/default_gcm_app_handler.cc',
'gcm_driver/default_gcm_app_handler.h', 'gcm_driver/default_gcm_app_handler.h',
'gcm_driver/gcm_account_mapper.cc',
'gcm_driver/gcm_account_mapper.h',
'gcm_driver/gcm_activity.cc', 'gcm_driver/gcm_activity.cc',
'gcm_driver/gcm_activity.h', 'gcm_driver/gcm_activity.h',
'gcm_driver/gcm_app_handler.cc', 'gcm_driver/gcm_app_handler.cc',
...@@ -55,6 +57,8 @@ ...@@ -55,6 +57,8 @@
'../google_apis/gcm/gcm.gyp:gcm', '../google_apis/gcm/gcm.gyp:gcm',
], ],
'sources!': [ 'sources!': [
'gcm_driver/gcm_account_mapper.cc',
'gcm_driver/gcm_account_mapper.h',
'gcm_driver/gcm_client_factory.cc', 'gcm_driver/gcm_client_factory.cc',
'gcm_driver/gcm_client_factory.h', 'gcm_driver/gcm_client_factory.h',
'gcm_driver/gcm_client_impl.cc', 'gcm_driver/gcm_client_impl.cc',
......
// 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 "base/bind.h"
#include "base/guid.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "components/gcm_driver/gcm_driver_desktop.h"
#include "google_apis/gcm/engine/gcm_store.h"
namespace gcm {
namespace {
const char kGCMAccountMapperSenderId[] = "745476177629";
const char kGCMAccountMapperAppId[] = "com.google.android.gms";
const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
const int kGCMUpdateIntervalHours = 24;
// Because adding an account mapping dependents on a fresh OAuth2 token, we
// allow the update to happen earlier than update due time, if it is within
// the early start time to take advantage of that token.
const int kGCMUpdateEarlyStartHours = 6;
const char kRegistrationIdMessgaeKey[] = "id";
const char kTokenMessageKey[] = "t";
const char kAccountMessageKey[] = "a";
const char kRemoveAccountKey[] = "r";
const char kRemoveAccountValue[] = "1";
std::string GenerateMessageID() {
return base::GenerateGUID();
}
} // namespace
GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
: gcm_driver_(gcm_driver),
clock_(new base::DefaultClock),
initialized_(false),
weak_ptr_factory_(this) {
}
GCMAccountMapper::~GCMAccountMapper() {
}
void GCMAccountMapper::Initialize(
const std::vector<AccountMapping>& account_mappings,
const std::string& registration_id) {
DCHECK(!initialized_);
initialized_ = true;
registration_id_ = registration_id;
accounts_ = account_mappings;
gcm_driver_->AddAppHandler(kGCMAccountMapperAppId, this);
// TODO(fgorski): if no registration ID, get registration ID.
}
void GCMAccountMapper::SetAccountTokens(
const std::vector<GCMClient::AccountTokenInfo> account_tokens) {
DCHECK(initialized_);
// Start from removing the old tokens, from all of the known accounts.
for (AccountMappings::iterator iter = accounts_.begin();
iter != accounts_.end();
++iter) {
iter->access_token.clear();
}
// Update the internal collection of mappings with the new tokens.
for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
account_tokens.begin();
token_iter != account_tokens.end();
++token_iter) {
AccountMapping* account_mapping =
FindMappingByAccountId(token_iter->account_id);
if (!account_mapping) {
AccountMapping new_mapping;
new_mapping.status = AccountMapping::NEW;
new_mapping.account_id = token_iter->account_id;
new_mapping.access_token = token_iter->access_token;
new_mapping.email = token_iter->email;
accounts_.push_back(new_mapping);
} else {
// Since we got a token for an account, drop the remove message and treat
// it as mapped.
if (account_mapping->status == AccountMapping::REMOVING) {
account_mapping->status = AccountMapping::MAPPED;
account_mapping->status_change_timestamp = base::Time();
account_mapping->last_message_id.clear();
}
account_mapping->email = token_iter->email;
account_mapping->access_token = token_iter->access_token;
}
}
// Decide what to do with each account (either start mapping, or start
// removing).
for (AccountMappings::iterator mappings_iter = accounts_.begin();
mappings_iter != accounts_.end();
++mappings_iter) {
if (mappings_iter->access_token.empty()) {
// Send a remove message if the account was not previously being removed,
// or it doesn't have a pending message, or the pending message is
// already expired, but OnSendError event was lost.
if (mappings_iter->status != AccountMapping::REMOVING ||
mappings_iter->last_message_id.empty() ||
IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
SendRemoveMappingMessage(*mappings_iter);
}
} else {
// A message is sent for all of the mappings considered NEW, or mappings
// that are ADDING, but have expired message (OnSendError event lost), or
// for those mapped accounts that can be refreshed.
if (mappings_iter->status == AccountMapping::NEW ||
(mappings_iter->status == AccountMapping::ADDING &&
IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
(mappings_iter->status == AccountMapping::MAPPED &&
CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
mappings_iter->last_message_id.clear();
SendAddMappingMessage(*mappings_iter);
}
}
}
}
void GCMAccountMapper::ShutdownHandler() {
gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId);
}
void GCMAccountMapper::OnMessage(const std::string& app_id,
const GCMClient::IncomingMessage& message) {
// Account message does not expect messages right now.
}
void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
// Account message does not expect messages right now.
}
void GCMAccountMapper::OnSendError(
const std::string& app_id,
const GCMClient::SendErrorDetails& send_error_details) {
DCHECK_EQ(app_id, kGCMAccountMapperAppId);
AccountMappings::iterator account_mapping_it =
FindMappingByMessageId(send_error_details.message_id);
if (account_mapping_it == accounts_.end())
return;
if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
DVLOG(1) << "Send error result different than TTL EXCEEDED: "
<< send_error_details.result << ". "
<< "Postponing the retry until a new batch of tokens arrives.";
return;
}
if (account_mapping_it->status == AccountMapping::REMOVING) {
// Another message to remove mapping can be sent immediately, because TTL
// for those is one day. No need to back off.
SendRemoveMappingMessage(*account_mapping_it);
} else {
if (account_mapping_it->status == AccountMapping::ADDING) {
// There is no mapping established, so we can remove the entry.
// Getting a fresh token will trigger a new attempt.
gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
accounts_.erase(account_mapping_it);
} else {
// Account is already MAPPED, we have to wait for another token.
account_mapping_it->last_message_id.clear();
gcm_driver_->UpdateAccountMapping(*account_mapping_it);
}
}
}
void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) {
DCHECK_EQ(app_id, kGCMAccountMapperAppId);
AccountMappings::iterator account_mapping_it =
FindMappingByMessageId(message_id);
DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
if (account_mapping_it == accounts_.end())
return;
// Here is where we advance a status of a mapping and persist or remove.
if (account_mapping_it->status == AccountMapping::REMOVING) {
// Message removing the account has been confirmed by the GCM, we can remove
// all the information related to the account (from memory and store).
gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
accounts_.erase(account_mapping_it);
} else {
// Mapping status is ADDING only when it is a first time mapping.
DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
account_mapping_it->status == AccountMapping::MAPPED);
// Account is marked as mapped with the current time.
account_mapping_it->status = AccountMapping::MAPPED;
account_mapping_it->status_change_timestamp = clock_->Now();
// There is no pending message for the account.
account_mapping_it->last_message_id.clear();
gcm_driver_->UpdateAccountMapping(*account_mapping_it);
}
}
bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
return app_id.compare(kGCMAccountMapperAppId) == 0;
}
void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
CreateAndSendMessage(account_mapping);
}
void GCMAccountMapper::SendRemoveMappingMessage(
AccountMapping& account_mapping) {
// We want to persist an account that is being removed as quickly as possible
// as well as clean up the last message information.
if (account_mapping.status != AccountMapping::REMOVING) {
account_mapping.status = AccountMapping::REMOVING;
account_mapping.status_change_timestamp = clock_->Now();
}
account_mapping.last_message_id.clear();
gcm_driver_->UpdateAccountMapping(account_mapping);
CreateAndSendMessage(account_mapping);
}
void GCMAccountMapper::CreateAndSendMessage(
const AccountMapping& account_mapping) {
GCMClient::OutgoingMessage outgoing_message;
outgoing_message.id = GenerateMessageID();
outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
outgoing_message.data[kAccountMessageKey] = account_mapping.email;
if (account_mapping.status == AccountMapping::REMOVING) {
outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
} else {
outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
}
gcm_driver_->Send(kGCMAccountMapperAppId,
kGCMAccountMapperSenderId,
outgoing_message,
base::Bind(&GCMAccountMapper::OnSendFinished,
weak_ptr_factory_.GetWeakPtr(),
account_mapping.account_id));
}
void GCMAccountMapper::OnSendFinished(const std::string& account_id,
const std::string& message_id,
GCMClient::Result result) {
// TODO(fgorski): Add another attempt, in case the QUEUE is not full.
if (result != GCMClient::SUCCESS)
return;
AccountMapping* account_mapping = FindMappingByAccountId(account_id);
DCHECK(account_mapping);
// If we are dealing with account with status NEW, it is the first time
// mapping, and we should mark it as ADDING.
if (account_mapping->status == AccountMapping::NEW) {
account_mapping->status = AccountMapping::ADDING;
account_mapping->status_change_timestamp = clock_->Now();
}
account_mapping->last_message_id = message_id;
gcm_driver_->UpdateAccountMapping(*account_mapping);
}
bool GCMAccountMapper::CanTriggerUpdate(
const base::Time& last_update_time) const {
return last_update_time +
base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
kGCMUpdateEarlyStartHours) <
clock_->Now();
}
bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
const AccountMapping& account_mapping) const {
int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
return account_mapping.status_change_timestamp +
base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
}
AccountMapping* GCMAccountMapper::FindMappingByAccountId(
const std::string& account_id) {
for (AccountMappings::iterator iter = accounts_.begin();
iter != accounts_.end();
++iter) {
if (iter->account_id == account_id)
return &*iter;
}
return NULL;
}
GCMAccountMapper::AccountMappings::iterator
GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
iter != accounts_.end();
++iter) {
if (iter->last_message_id == message_id)
return iter;
}
return accounts_.end();
}
void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
clock_ = clock.Pass();
}
} // namespace gcm
// 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.
#ifndef COMPONENTS_GCM_DRIVER_GCM_ACCOUNT_MAPPER_H_
#define COMPONENTS_GCM_DRIVER_GCM_ACCOUNT_MAPPER_H_
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/gcm_driver/gcm_app_handler.h"
#include "components/gcm_driver/gcm_client.h"
#include "google_apis/gcm/engine/account_mapping.h"
namespace base {
class Clock;
}
namespace gcm {
class GCMDriver;
// Class for mapping signed-in GAIA accounts to the GCM Device ID.
class GCMAccountMapper : public GCMAppHandler {
public:
// List of account mappings.
typedef std::vector<AccountMapping> AccountMappings;
explicit GCMAccountMapper(GCMDriver* gcm_driver);
virtual ~GCMAccountMapper();
void Initialize(const AccountMappings& account_mappings,
const std::string& registration_id);
// Called by AccountTracker, when a new list of account tokens is available.
// This will cause a refresh of account mappings and sending updates to GCM.
void SetAccountTokens(
const std::vector<GCMClient::AccountTokenInfo> account_tokens);
// Implementation of GCMAppHandler:
virtual void ShutdownHandler() OVERRIDE;
virtual void OnMessage(const std::string& app_id,
const GCMClient::IncomingMessage& message) OVERRIDE;
virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE;
virtual void OnSendError(
const std::string& app_id,
const GCMClient::SendErrorDetails& send_error_details) OVERRIDE;
virtual void OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) OVERRIDE;
virtual bool CanHandle(const std::string& app_id) const OVERRIDE;
private:
friend class GCMAccountMapperTest;
typedef std::map<std::string, GCMClient::OutgoingMessage> OutgoingMessages;
// Informs GCM of an added or refreshed account mapping.
void SendAddMappingMessage(AccountMapping& account_mapping);
// Informs GCM of a removed account mapping.
void SendRemoveMappingMessage(AccountMapping& account_mapping);
void CreateAndSendMessage(const AccountMapping& account_mapping);
// Callback for sending a message.
void OnSendFinished(const std::string& account_id,
const std::string& message_id,
GCMClient::Result result);
// Checks whether the update can be triggered now. If the current time is
// within reasonable time (6 hours) of when the update is due, we want to
// trigger the update immediately to take advantage of a fresh OAuth2 token.
bool CanTriggerUpdate(const base::Time& last_update_time) const;
// Checks whether last status change is older than a TTL of a message.
bool IsLastStatusChangeOlderThanTTL(
const AccountMapping& account_mapping) const;
// Finds an account mapping in |accounts_| by |account_id|.
AccountMapping* FindMappingByAccountId(const std::string& account_id);
// Finds an account mapping in |accounts_| by |message_id|.
// Returns iterator that can be used to delete the account.
AccountMappings::iterator FindMappingByMessageId(
const std::string& message_id);
// Sets the clock for testing.
void SetClockForTesting(scoped_ptr<base::Clock> clock);
// GCMDriver owns GCMAccountMapper.
GCMDriver* gcm_driver_;
// Clock for timestamping status changes.
scoped_ptr<base::Clock> clock_;
// Currnetly tracked account mappings.
AccountMappings accounts_;
// GCM Registration ID of the account mapper.
std::string registration_id_;
bool initialized_;
base::WeakPtrFactory<GCMAccountMapper> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(GCMAccountMapper);
};
} // namespace gcm
#endif // COMPONENTS_GCM_DRIVER_GCM_ACCOUNT_MAPPER_H_
// 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 "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 kGCMAccountMapperAppId[] = "com.google.android.gms";
const char kRegistrationId[] = "reg_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;
}
class CustomFakeGCMDriver : public FakeGCMDriver {
public:
CustomFakeGCMDriver();
virtual ~CustomFakeGCMDriver();
virtual void UpdateAccountMapping(
const AccountMapping& account_mapping) OVERRIDE;
virtual void RemoveAccountMapping(const std::string& account_id) OVERRIDE;
virtual void AddAppHandler(const std::string& app_id,
GCMAppHandler* handler) OVERRIDE;
virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE;
void CompleteSend(const std::string& message_id, GCMClient::Result result);
void SendAcknowledged(const std::string& message_id);
void MessageSendError(const std::string& message_id);
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_;
}
protected:
virtual void SendImpl(const std::string& app_id,
const std::string& receiver_id,
const GCMClient::OutgoingMessage& message) OVERRIDE;
private:
AccountMapping account_mapping_;
std::string last_message_id_;
std::string last_removed_account_id_;
};
CustomFakeGCMDriver::CustomFakeGCMDriver() {
}
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::AddAppHandler(const std::string& app_id,
GCMAppHandler* handler) {
GCMDriver::AddAppHandler(app_id, handler);
}
void CustomFakeGCMDriver::RemoveAppHandler(const std::string& app_id) {
GCMDriver::RemoveAppHandler(app_id);
}
void CustomFakeGCMDriver::CompleteSend(const std::string& message_id,
GCMClient::Result result) {
SendFinished(kGCMAccountMapperAppId, message_id, result);
}
void CustomFakeGCMDriver::SendAcknowledged(const std::string& message_id) {
GetAppHandler(kGCMAccountMapperAppId)
->OnSendAcknowledged(kGCMAccountMapperAppId, message_id);
}
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 GCMClient::OutgoingMessage& message) {
DCHECK_EQ(kGCMAccountMapperAppId, app_id);
DCHECK_EQ(kGCMAccountMapperSenderId, receiver_id);
last_message_id_ = message.id;
}
} // namespace
class GCMAccountMapperTest : public testing::Test {
public:
GCMAccountMapperTest();
virtual ~GCMAccountMapperTest();
void Restart();
const std::vector<AccountMapping>& GetAccounts() const {
return account_mapper_->accounts_;
}
GCMAccountMapper* mapper() { return account_mapper_.get(); }
CustomFakeGCMDriver& gcm_driver() { return gcm_driver_; }
base::SimpleTestClock* clock() { return clock_; }
private:
CustomFakeGCMDriver gcm_driver_;
scoped_ptr<GCMAccountMapper> account_mapper_;
base::SimpleTestClock* clock_;
};
GCMAccountMapperTest::GCMAccountMapperTest() {
Restart();
}
GCMAccountMapperTest::~GCMAccountMapperTest() {
}
void GCMAccountMapperTest::Restart() {
if (account_mapper_)
account_mapper_->ShutdownHandler();
account_mapper_.reset(new GCMAccountMapper(&gcm_driver_));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock_ = clock.get();
account_mapper_->SetClockForTesting(clock.PassAs<base::Clock>());
}
// Tests the initialization of account mappings (from the store) when empty.
TEST_F(GCMAccountMapperTest, InitializeAccountMappingsEmpty) {
std::vector<AccountMapping> account_mappings;
mapper()->Initialize(account_mappings, "");
EXPECT_TRUE(GetAccounts().empty());
}
// Tests the initialization of account mappings (from the store).
TEST_F(GCMAccountMapperTest, InitializeAccountMappings) {
std::vector<AccountMapping> 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);
mapper()->Initialize(account_mappings, "");
std::vector<AccountMapping> mappings = GetAccounts();
EXPECT_EQ(2UL, mappings.size());
std::vector<AccountMapping>::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 the part where a new account is added with a token, to the point when
// GCM message is sent.
TEST_F(GCMAccountMapperTest, AddMappingToMessageSent) {
mapper()->Initialize(GCMAccountMapper::AccountMappings(), kRegistrationId);
std::vector<GCMClient::AccountTokenInfo> account_tokens;
GCMClient::AccountTokenInfo account_token = MakeAccountTokenInfo("acc_id");
account_tokens.push_back(account_token);
mapper()->SetAccountTokens(account_tokens);
std::vector<AccountMapping> mappings = GetAccounts();
EXPECT_EQ(1UL, mappings.size());
std::vector<AccountMapping>::const_iterator iter = mappings.begin();
EXPECT_EQ("acc_id", 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) {
mapper()->Initialize(GCMAccountMapper::AccountMappings(), kRegistrationId);
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);
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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) {
mapper()->Initialize(GCMAccountMapper::AccountMappings(), kRegistrationId);
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().SendAcknowledged(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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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) {
mapper()->Initialize(GCMAccountMapper::AccountMappings(), kRegistrationId);
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();
std::vector<AccountMapping> stored_mappings;
stored_mappings.push_back(gcm_driver().last_account_mapping());
mapper()->Initialize(stored_mappings, kRegistrationId);
clock()->SetNow(base::Time::Now());
gcm_driver().SendAcknowledged(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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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) {
mapper()->Initialize(GCMAccountMapper::AccountMappings(), kRegistrationId);
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);
mapper()->Initialize(stored_mappings, kRegistrationId);
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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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);
mapper()->Initialize(stored_mappings, kRegistrationId);
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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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);
mapper()->Initialize(stored_mappings, kRegistrationId);
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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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);
mapper()->Initialize(stored_mappings, kRegistrationId);
clock()->SetNow(base::Time::Now());
mapper()->SetAccountTokens(std::vector<GCMClient::AccountTokenInfo>());
gcm_driver().CompleteSend(gcm_driver().last_message_id(), GCMClient::SUCCESS);
gcm_driver().SendAcknowledged(gcm_driver().last_message_id());
EXPECT_EQ(mapping.account_id, gcm_driver().last_removed_account_id());
std::vector<AccountMapping> 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);
mapper()->Initialize(stored_mappings, kRegistrationId);
gcm_driver().SendAcknowledged("remove_message_id");
EXPECT_EQ(mapping.account_id, gcm_driver().last_removed_account_id());
std::vector<AccountMapping> 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);
mapper()->Initialize(stored_mappings, kRegistrationId);
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());
std::vector<AccountMapping> mappings = GetAccounts();
std::vector<AccountMapping>::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());
}
} // namespace gcm
...@@ -141,6 +141,13 @@ class GCMClient { ...@@ -141,6 +141,13 @@ class GCMClient {
RecordedActivities recorded_activities; RecordedActivities recorded_activities;
}; };
// Information about account.
struct AccountTokenInfo {
std::string account_id;
std::string email;
std::string access_token;
};
// A delegate interface that allows the GCMClient instance to interact with // A delegate interface that allows the GCMClient instance to interact with
// its caller, i.e. notifying asynchronous event. // its caller, i.e. notifying asynchronous event.
class Delegate { class Delegate {
......
...@@ -12,39 +12,44 @@ namespace gcm { ...@@ -12,39 +12,44 @@ namespace gcm {
namespace { namespace {
const char kSeparator[] = "&"; const char kSeparator[] = "&";
uint32 kEmailIndex = 0; const uint32 kEmailIndex = 0;
uint32 kMappingChangeTimestampIndex = 1; const uint32 kStatusIndex = 1;
uint32 kMessageTypeIndex = 2; const uint32 kStatusChangeTimestampIndex = 2;
uint32 kMessageIdIndex = 3; const uint32 kSizeWithNoMessage = kStatusChangeTimestampIndex + 1;
uint32 kSizeWithNoMessage = kMessageTypeIndex + 1; const uint32 kMessageIdIndex = 3;
uint32 kSizeWithMessage = kMessageIdIndex + 1; const uint32 kSizeWithMessage = kMessageIdIndex + 1;
const char kMessageTypeNoneString[] = "none"; const char kStatusNew[] = "new";
const char kMessageTypeAddString[] = "add"; const char kStatusAdding[] = "adding";
const char kMessageTypeRemoveString[] = "remove"; const char kStatusMapped[] = "mapped";
const char kStatusRemoving[] = "removing";
std::string MessageTypeToString(AccountMapping::MessageType type) {
switch (type) { std::string StatusToString(AccountMapping::MappingStatus status) {
case AccountMapping::MSG_NONE: switch (status) {
return kMessageTypeNoneString; case AccountMapping::NEW:
case AccountMapping::MSG_ADD: return kStatusNew;
return kMessageTypeAddString; case AccountMapping::ADDING:
case AccountMapping::MSG_REMOVE: return kStatusAdding;
return kMessageTypeRemoveString; case AccountMapping::MAPPED:
return kStatusMapped;
case AccountMapping::REMOVING:
return kStatusRemoving;
default: default:
NOTREACHED(); NOTREACHED();
} }
return std::string(); return std::string();
} }
bool StringToMessageType(const std::string& type_str, bool StringToStatus(const std::string& status_str,
AccountMapping::MessageType* type) { AccountMapping::MappingStatus* status) {
if (type_str.compare(kMessageTypeAddString) == 0) if (status_str.compare(kStatusAdding) == 0)
*type = AccountMapping::MSG_ADD; *status = AccountMapping::ADDING;
else if (type_str.compare(kMessageTypeRemoveString) == 0) else if (status_str.compare(kStatusMapped) == 0)
*type = AccountMapping::MSG_REMOVE; *status = AccountMapping::MAPPED;
else if (type_str.compare(kMessageTypeNoneString) == 0) else if (status_str.compare(kStatusRemoving) == 0)
*type = AccountMapping::MSG_NONE; *status = AccountMapping::REMOVING;
else if (status_str.compare(kStatusNew) == 0)
*status = AccountMapping::NEW;
else else
return false; return false;
...@@ -53,7 +58,7 @@ bool StringToMessageType(const std::string& type_str, ...@@ -53,7 +58,7 @@ bool StringToMessageType(const std::string& type_str,
} // namespace } // namespace
AccountMapping::AccountMapping() { AccountMapping::AccountMapping() : status(NEW) {
} }
AccountMapping::~AccountMapping() { AccountMapping::~AccountMapping() {
...@@ -63,10 +68,10 @@ std::string AccountMapping::SerializeAsString() const { ...@@ -63,10 +68,10 @@ std::string AccountMapping::SerializeAsString() const {
std::string value; std::string value;
value.append(email); value.append(email);
value.append(kSeparator); value.append(kSeparator);
value.append(base::Int64ToString(status_change_timestamp.ToInternalValue())); value.append(StatusToString(status));
value.append(kSeparator); value.append(kSeparator);
value.append(MessageTypeToString(last_message_type)); value.append(base::Int64ToString(status_change_timestamp.ToInternalValue()));
if (last_message_type != MSG_NONE) { if (!last_message_id.empty()) {
value.append(kSeparator); value.append(kSeparator);
value.append(last_message_id); value.append(last_message_id);
} }
...@@ -83,49 +88,40 @@ bool AccountMapping::ParseFromString(const std::string& value) { ...@@ -83,49 +88,40 @@ bool AccountMapping::ParseFromString(const std::string& value) {
} }
if (values[kEmailIndex].empty() || if (values[kEmailIndex].empty() ||
values[kMappingChangeTimestampIndex].empty() || values[kStatusChangeTimestampIndex].empty() ||
values[kMessageTypeIndex].empty()) { values[kStatusIndex].empty()) {
return false; return false;
} }
if (values.size() == kSizeWithMessage && values[kMessageIdIndex].empty()) { if (values.size() == kSizeWithMessage && values[kMessageIdIndex].empty())
return false; return false;
}
MessageType message_type; MappingStatus temp_status;
if (!StringToMessageType(values[kMessageTypeIndex], &message_type)) if (!StringToStatus(values[kStatusIndex], &temp_status))
return false; return false;
if ((message_type == MSG_NONE && values.size() == kSizeWithMessage) || if (values.size() == kSizeWithNoMessage &&
(message_type != MSG_NONE && values.size() != kSizeWithMessage)) { (temp_status == REMOVING || temp_status == ADDING)) {
return false; return false;
} }
last_message_type = message_type;
int64 status_change_ts_internal = 0LL; int64 status_change_ts_internal = 0LL;
if (!base::StringToInt64(values[kMappingChangeTimestampIndex], if (!base::StringToInt64(values[kStatusChangeTimestampIndex],
&status_change_ts_internal)) { &status_change_ts_internal)) {
return false; return false;
} }
if (status_change_ts_internal == 0LL) status_change_timestamp =
status = ADDING; base::Time::FromInternalValue(status_change_ts_internal);
else if (last_message_type == MSG_REMOVE) status = temp_status;
status = REMOVING; email = values[kEmailIndex];
else access_token.clear();
status = MAPPED;
if (values.size() == kSizeWithMessage) if (values.size() == kSizeWithMessage)
last_message_id = values[kMessageIdIndex]; last_message_id = values[kMessageIdIndex];
else else
last_message_id.clear(); last_message_id.clear();
email = values[kEmailIndex];
status_change_timestamp =
base::Time::FromInternalValue(status_change_ts_internal);
access_token.clear();
return true; return true;
} }
......
...@@ -25,16 +25,6 @@ struct GCM_EXPORT AccountMapping { ...@@ -25,16 +25,6 @@ struct GCM_EXPORT AccountMapping {
// reached the GCM. // reached the GCM.
REMOVING, // Account is removed, but a message removing the mapping has not REMOVING, // Account is removed, but a message removing the mapping has not
// been confirmed yet. // been confirmed yet.
REMOVED, // Account is removed, and at least one message has been
// confirmed to have reached the GCM.
};
// Indicates whether a message, if sent, was adding or removing account
// mapping.
enum MessageType {
MSG_NONE, // No message has been sent.
MSG_ADD, // Account was mapped to device by the message.
MSG_REMOVE, // Account mapping to device was removed by the message.
}; };
AccountMapping(); AccountMapping();
...@@ -57,8 +47,6 @@ struct GCM_EXPORT AccountMapping { ...@@ -57,8 +47,6 @@ struct GCM_EXPORT AccountMapping {
MappingStatus status; MappingStatus status;
// Time of the mapping status change. // Time of the mapping status change.
base::Time status_change_timestamp; base::Time status_change_timestamp;
// Type of the last mapping message sent to GCM.
MessageType last_message_type;
// ID of the last mapping message sent to GCM. // ID of the last mapping message sent to GCM.
std::string last_message_id; std::string last_message_id;
}; };
......
...@@ -17,66 +17,91 @@ TEST(AccountMappingTest, SerializeAccountMapping) { ...@@ -17,66 +17,91 @@ TEST(AccountMappingTest, SerializeAccountMapping) {
account_mapping.account_id = "acc_id"; account_mapping.account_id = "acc_id";
account_mapping.email = "test@example.com"; account_mapping.email = "test@example.com";
account_mapping.access_token = "access_token"; account_mapping.access_token = "access_token";
account_mapping.status = AccountMapping::ADDING; account_mapping.status = AccountMapping::NEW;
account_mapping.status_change_timestamp = base::Time(); account_mapping.status_change_timestamp = base::Time();
account_mapping.last_message_id.clear();
EXPECT_EQ("test@example.com&new&0", account_mapping.SerializeAsString());
account_mapping.status = AccountMapping::ADDING;
account_mapping.status_change_timestamp =
base::Time::FromInternalValue(1305797421259977LL);
account_mapping.last_message_id = "last_message_id_1"; account_mapping.last_message_id = "last_message_id_1";
account_mapping.last_message_type = AccountMapping::MSG_ADD;
EXPECT_EQ("test@example.com&0&add&last_message_id_1", EXPECT_EQ("test@example.com&adding&1305797421259977&last_message_id_1",
account_mapping.SerializeAsString());
account_mapping.status = AccountMapping::MAPPED;
EXPECT_EQ("test@example.com&mapped&1305797421259977&last_message_id_1",
account_mapping.SerializeAsString());
account_mapping.last_message_id.clear();
EXPECT_EQ("test@example.com&mapped&1305797421259977",
account_mapping.SerializeAsString()); account_mapping.SerializeAsString());
account_mapping.account_id = "acc_id2"; account_mapping.account_id = "acc_id2";
account_mapping.email = "test@gmail.com"; account_mapping.email = "test@gmail.com";
account_mapping.access_token = "access_token"; // should be ignored. account_mapping.access_token = "access_token"; // should be ignored.
account_mapping.status = AccountMapping::MAPPED; // should be ignored. account_mapping.status = AccountMapping::REMOVING;
account_mapping.status_change_timestamp =
base::Time::FromInternalValue(1305797421259977LL);
account_mapping.last_message_id = "last_message_id_2"; account_mapping.last_message_id = "last_message_id_2";
account_mapping.last_message_type = AccountMapping::MSG_REMOVE;
EXPECT_EQ("test@gmail.com&1305797421259977&remove&last_message_id_2",
account_mapping.SerializeAsString());
account_mapping.last_message_type = AccountMapping::MSG_NONE;
EXPECT_EQ("test@gmail.com&1305797421259977&none", EXPECT_EQ("test@gmail.com&removing&1305797421259977&last_message_id_2",
account_mapping.SerializeAsString()); account_mapping.SerializeAsString());
} }
TEST(AccountMappingTest, DeserializeAccountMapping) { TEST(AccountMappingTest, DeserializeAccountMapping) {
AccountMapping account_mapping; AccountMapping account_mapping;
account_mapping.account_id = "acc_id"; account_mapping.account_id = "acc_id";
EXPECT_TRUE(account_mapping.ParseFromString(
"test@example.com&0&add&last_message_id_1")); EXPECT_TRUE(account_mapping.ParseFromString("test@example.com&new&0"));
EXPECT_EQ("acc_id", account_mapping.account_id); EXPECT_EQ("acc_id", account_mapping.account_id);
EXPECT_EQ("test@example.com", account_mapping.email); EXPECT_EQ("test@example.com", account_mapping.email);
EXPECT_TRUE(account_mapping.access_token.empty()); EXPECT_TRUE(account_mapping.access_token.empty());
EXPECT_EQ(AccountMapping::ADDING, account_mapping.status); EXPECT_EQ(AccountMapping::NEW, account_mapping.status);
EXPECT_EQ(base::Time(), account_mapping.status_change_timestamp); EXPECT_EQ(base::Time(), account_mapping.status_change_timestamp);
EXPECT_EQ(AccountMapping::MSG_ADD, account_mapping.last_message_type); EXPECT_TRUE(account_mapping.last_message_id.empty());
EXPECT_EQ("last_message_id_1", account_mapping.last_message_id);
EXPECT_TRUE(account_mapping.ParseFromString( EXPECT_TRUE(account_mapping.ParseFromString(
"test@gmail.com&1305797421259977&remove&last_message_id_2")); "test@gmail.com&adding&1305797421259977&last_message_id_1"));
EXPECT_EQ("acc_id", account_mapping.account_id); EXPECT_EQ("acc_id", account_mapping.account_id);
EXPECT_EQ("test@gmail.com", account_mapping.email); EXPECT_EQ("test@gmail.com", account_mapping.email);
EXPECT_TRUE(account_mapping.access_token.empty()); EXPECT_TRUE(account_mapping.access_token.empty());
EXPECT_EQ(AccountMapping::REMOVING, account_mapping.status); EXPECT_EQ(AccountMapping::ADDING, account_mapping.status);
EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL), EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL),
account_mapping.status_change_timestamp); account_mapping.status_change_timestamp);
EXPECT_EQ(AccountMapping::MSG_REMOVE, account_mapping.last_message_type); EXPECT_EQ("last_message_id_1", account_mapping.last_message_id);
EXPECT_EQ("last_message_id_2", account_mapping.last_message_id);
EXPECT_TRUE(account_mapping.ParseFromString( EXPECT_TRUE(account_mapping.ParseFromString(
"test@gmail.com&1305797421259977&none")); "test@example.com&mapped&1305797421259977"));
EXPECT_EQ("acc_id", account_mapping.account_id);
EXPECT_EQ("test@example.com", account_mapping.email);
EXPECT_TRUE(account_mapping.access_token.empty());
EXPECT_EQ(AccountMapping::MAPPED, account_mapping.status);
EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL),
account_mapping.status_change_timestamp);
EXPECT_TRUE(account_mapping.last_message_id.empty());
EXPECT_TRUE(account_mapping.ParseFromString(
"test@gmail.com&mapped&1305797421259977&last_message_id_1"));
EXPECT_EQ("acc_id", account_mapping.account_id); EXPECT_EQ("acc_id", account_mapping.account_id);
EXPECT_EQ("test@gmail.com", account_mapping.email); EXPECT_EQ("test@gmail.com", account_mapping.email);
EXPECT_TRUE(account_mapping.access_token.empty()); EXPECT_TRUE(account_mapping.access_token.empty());
EXPECT_EQ(AccountMapping::MAPPED, account_mapping.status); EXPECT_EQ(AccountMapping::MAPPED, account_mapping.status);
EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL), EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL),
account_mapping.status_change_timestamp); account_mapping.status_change_timestamp);
EXPECT_EQ(AccountMapping::MSG_NONE, account_mapping.last_message_type); EXPECT_EQ("last_message_id_1", account_mapping.last_message_id);
EXPECT_EQ("", account_mapping.last_message_id);
EXPECT_TRUE(account_mapping.ParseFromString(
"test@gmail.com&removing&1305797421259977&last_message_id_2"));
EXPECT_EQ("acc_id", account_mapping.account_id);
EXPECT_EQ("test@gmail.com", account_mapping.email);
EXPECT_TRUE(account_mapping.access_token.empty());
EXPECT_EQ(AccountMapping::REMOVING, account_mapping.status);
EXPECT_EQ(base::Time::FromInternalValue(1305797421259977LL),
account_mapping.status_change_timestamp);
EXPECT_EQ("last_message_id_2", account_mapping.last_message_id);
} }
TEST(AccountMappingTest, DeserializeAccountMappingInvalidInput) { TEST(AccountMappingTest, DeserializeAccountMappingInvalidInput) {
...@@ -84,38 +109,37 @@ TEST(AccountMappingTest, DeserializeAccountMappingInvalidInput) { ...@@ -84,38 +109,37 @@ TEST(AccountMappingTest, DeserializeAccountMappingInvalidInput) {
account_mapping.account_id = "acc_id"; account_mapping.account_id = "acc_id";
// Too many agruments. // Too many agruments.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935" "test@example.com&adding&1305797421259935&last_message_id_1&stuff_here"));
"&add&last_message_id_1&stuff_here"));
// Too few arguments. // Too few arguments.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&remove")); "test@example.com&removing&1305797421259935"));
// Too few arguments. // Too few arguments.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935")); "test@example.com&adding&1305797421259935"));
// Missing email. // Too few arguments.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"&1305797421259935&remove&last_message_id_2")); "test@example.com&new"));
// Missing mapping status change timestamp. // Too few arguments.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@gmail.com&&remove&last_message_id_2")); "test@example.com&mapped"));
// Last mapping status change timestamp not parseable. // Missing email.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@gmail.com&remove&asdfjkl&last_message_id_2")); "&remove&1305797421259935&last_message_id_2"));
// Missing message type. // Missing mapping status.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&&last_message_id_2")); "test@example.com&&1305797421259935&last_message_id_2"));
// Unkown message type. // Unkown mapping status.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&random&last_message_id_2")); "test@example.com&random&1305797421259935&last_message_id_2"));
// Message type is none when message details specified. // Missing mapping status change timestamp.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&none&last_message_id_2")); "test@gmail.com&removing&&last_message_id_2"));
// Message type is messed up. // Last mapping status change timestamp not parseable.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&random")); "test@gmail.com&removing&asdfjkl&last_message_id_2"));
// Missing last message ID. // Missing last message ID.
EXPECT_FALSE(account_mapping.ParseFromString( EXPECT_FALSE(account_mapping.ParseFromString(
"test@example.com&1305797421259935&remove&")); "test@example.com&removing&1305797421259935&"));
} }
} // namespace } // namespace
......
...@@ -532,7 +532,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) { ...@@ -532,7 +532,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) {
account_mapping1.access_token = "account_token1"; account_mapping1.access_token = "account_token1";
account_mapping1.status = AccountMapping::ADDING; account_mapping1.status = AccountMapping::ADDING;
account_mapping1.status_change_timestamp = base::Time(); account_mapping1.status_change_timestamp = base::Time();
account_mapping1.last_message_type = AccountMapping::MSG_ADD;
account_mapping1.last_message_id = "message_1"; account_mapping1.last_message_id = "message_1";
AccountMapping account_mapping2; AccountMapping account_mapping2;
...@@ -542,7 +541,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) { ...@@ -542,7 +541,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) {
account_mapping2.status = AccountMapping::REMOVING; account_mapping2.status = AccountMapping::REMOVING;
account_mapping2.status_change_timestamp = account_mapping2.status_change_timestamp =
base::Time::FromInternalValue(1305734521259935LL); base::Time::FromInternalValue(1305734521259935LL);
account_mapping2.last_message_type = AccountMapping::MSG_REMOVE;
account_mapping2.last_message_id = "message_2"; account_mapping2.last_message_id = "message_2";
gcm_store->AddAccountMapping( gcm_store->AddAccountMapping(
...@@ -569,7 +567,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) { ...@@ -569,7 +567,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) {
EXPECT_EQ(AccountMapping::ADDING, iter->second.status); EXPECT_EQ(AccountMapping::ADDING, iter->second.status);
EXPECT_EQ(account_mapping1.status_change_timestamp, EXPECT_EQ(account_mapping1.status_change_timestamp,
iter->second.status_change_timestamp); iter->second.status_change_timestamp);
EXPECT_EQ(account_mapping1.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_mapping1.last_message_id, iter->second.last_message_id); EXPECT_EQ(account_mapping1.last_message_id, iter->second.last_message_id);
++iter; ++iter;
EXPECT_EQ("account_id_2", iter->first); EXPECT_EQ("account_id_2", iter->first);
...@@ -579,7 +576,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) { ...@@ -579,7 +576,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) {
EXPECT_EQ(AccountMapping::REMOVING, iter->second.status); EXPECT_EQ(AccountMapping::REMOVING, iter->second.status);
EXPECT_EQ(account_mapping2.status_change_timestamp, EXPECT_EQ(account_mapping2.status_change_timestamp,
iter->second.status_change_timestamp); iter->second.status_change_timestamp);
EXPECT_EQ(account_mapping2.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_mapping2.last_message_id, iter->second.last_message_id); EXPECT_EQ(account_mapping2.last_message_id, iter->second.last_message_id);
gcm_store->RemoveAccountMapping( gcm_store->RemoveAccountMapping(
...@@ -601,7 +597,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) { ...@@ -601,7 +597,6 @@ TEST_F(GCMStoreImplTest, AccountMapping) {
EXPECT_EQ(AccountMapping::REMOVING, iter->second.status); EXPECT_EQ(AccountMapping::REMOVING, iter->second.status);
EXPECT_EQ(account_mapping2.status_change_timestamp, EXPECT_EQ(account_mapping2.status_change_timestamp,
iter->second.status_change_timestamp); iter->second.status_change_timestamp);
EXPECT_EQ(account_mapping2.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_mapping2.last_message_id, iter->second.last_message_id); EXPECT_EQ(account_mapping2.last_message_id, iter->second.last_message_id);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment