| // Copyright (c) 2012 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 "chrome/browser/invalidation/invalidator_storage.h" |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/message_loop/message_loop_proxy.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_pref_service_syncable.h" |
| #include "sync/internal_api/public/base/invalidation_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using syncer::InvalidationStateMap; |
| |
| namespace { |
| |
| const char kSourceKey[] = "source"; |
| const char kNameKey[] = "name"; |
| const char kMaxVersionKey[] = "max-version"; |
| const char kPayloadKey[] = "payload"; |
| const char kCurrentAckHandleKey[] = "current-ack"; |
| const char kExpectedAckHandleKey[] = "expected-ack"; |
| |
| const int kChromeSyncSourceId = 1004; |
| |
| void GenerateAckHandlesTestHelper(syncer::AckHandleMap* output, |
| const syncer::AckHandleMap& input) { |
| *output = input; |
| } |
| |
| } // namespace |
| |
| namespace invalidation { |
| |
| class InvalidatorStorageTest : public testing::Test { |
| public: |
| InvalidatorStorageTest() |
| : kBookmarksId_(kChromeSyncSourceId, "BOOKMARK"), |
| kPreferencesId_(kChromeSyncSourceId, "PREFERENCE"), |
| kAppNotificationsId_(kChromeSyncSourceId, "APP_NOTIFICATION"), |
| kAutofillId_(kChromeSyncSourceId, "AUTOFILL") {} |
| |
| virtual void SetUp() { |
| InvalidatorStorage::RegisterProfilePrefs(pref_service_.registry()); |
| } |
| |
| protected: |
| TestingPrefServiceSyncable pref_service_; |
| |
| const invalidation::ObjectId kBookmarksId_; |
| const invalidation::ObjectId kPreferencesId_; |
| const invalidation::ObjectId kAppNotificationsId_; |
| const invalidation::ObjectId kAutofillId_; |
| |
| base::MessageLoop loop_; |
| }; |
| |
| // Set invalidation states for various keys and verify that they are written and |
| // read back correctly. |
| TEST_F(InvalidatorStorageTest, SetMaxVersionAndPayload) { |
| InvalidatorStorage storage(&pref_service_); |
| |
| InvalidationStateMap expected_states; |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| |
| expected_states[kBookmarksId_].version = 2; |
| expected_states[kBookmarksId_].payload = "hello"; |
| storage.SetMaxVersionAndPayload(kBookmarksId_, 2, "hello"); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| |
| expected_states[kPreferencesId_].version = 5; |
| storage.SetMaxVersionAndPayload(kPreferencesId_, 5, std::string()); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| |
| expected_states[kAppNotificationsId_].version = 3; |
| expected_states[kAppNotificationsId_].payload = "world"; |
| storage.SetMaxVersionAndPayload(kAppNotificationsId_, 3, "world"); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| |
| expected_states[kAppNotificationsId_].version = 4; |
| expected_states[kAppNotificationsId_].payload = "again"; |
| storage.SetMaxVersionAndPayload(kAppNotificationsId_, 4, "again"); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| } |
| |
| // Forgetting an entry should cause that entry to be deleted. |
| TEST_F(InvalidatorStorageTest, Forget) { |
| InvalidatorStorage storage(&pref_service_); |
| EXPECT_TRUE(storage.GetAllInvalidationStates().empty()); |
| |
| InvalidationStateMap expected_states; |
| expected_states[kBookmarksId_].version = 2; |
| expected_states[kBookmarksId_].payload = "a"; |
| expected_states[kPreferencesId_].version = 5; |
| expected_states[kPreferencesId_].payload = "b"; |
| storage.SetMaxVersionAndPayload(kBookmarksId_, 2, "a"); |
| storage.SetMaxVersionAndPayload(kPreferencesId_, 5, "b"); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| |
| expected_states.erase(kPreferencesId_); |
| syncer::ObjectIdSet to_forget; |
| to_forget.insert(kPreferencesId_); |
| storage.Forget(to_forget); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| } |
| |
| // Clearing the storage should erase all version map entries, bootstrap data, |
| // and the client ID. |
| TEST_F(InvalidatorStorageTest, Clear) { |
| InvalidatorStorage storage(&pref_service_); |
| EXPECT_TRUE(storage.GetAllInvalidationStates().empty()); |
| EXPECT_TRUE(storage.GetBootstrapData().empty()); |
| EXPECT_TRUE(storage.GetInvalidatorClientId().empty()); |
| |
| storage.SetInvalidatorClientId("fake_id"); |
| EXPECT_EQ("fake_id", storage.GetInvalidatorClientId()); |
| |
| storage.SetBootstrapData("test"); |
| EXPECT_EQ("test", storage.GetBootstrapData()); |
| |
| { |
| InvalidationStateMap expected_states; |
| expected_states[kAppNotificationsId_].version = 3; |
| storage.SetMaxVersionAndPayload(kAppNotificationsId_, 3, std::string()); |
| EXPECT_EQ(expected_states, storage.GetAllInvalidationStates()); |
| } |
| |
| storage.Clear(); |
| |
| EXPECT_TRUE(storage.GetAllInvalidationStates().empty()); |
| EXPECT_TRUE(storage.GetBootstrapData().empty()); |
| EXPECT_TRUE(storage.GetInvalidatorClientId().empty()); |
| } |
| |
| TEST_F(InvalidatorStorageTest, SerializeEmptyMap) { |
| InvalidationStateMap empty_map; |
| base::ListValue list; |
| InvalidatorStorage::SerializeToList(empty_map, &list); |
| EXPECT_TRUE(list.empty()); |
| } |
| |
| // Make sure we don't choke on a variety of malformed input. |
| TEST_F(InvalidatorStorageTest, DeserializeFromListInvalidFormat) { |
| InvalidationStateMap map; |
| base::ListValue list_with_invalid_format; |
| DictionaryValue* value; |
| |
| // The various cases below use distinct values to make it easier to track down |
| // failures. |
| value = new DictionaryValue(); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString("completely", "invalid"); |
| list_with_invalid_format.Append(value); |
| |
| // Missing two required fields |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "10"); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString(kNameKey, "missing source and version"); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString(kMaxVersionKey, "3"); |
| list_with_invalid_format.Append(value); |
| |
| // Missing one required field |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "14"); |
| value->SetString(kNameKey, "missing version"); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "233"); |
| value->SetString(kMaxVersionKey, "5"); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString(kNameKey, "missing source"); |
| value->SetString(kMaxVersionKey, "25"); |
| list_with_invalid_format.Append(value); |
| |
| // Invalid values in fields |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "a"); |
| value->SetString(kNameKey, "bad source"); |
| value->SetString(kMaxVersionKey, "12"); |
| list_with_invalid_format.Append(value); |
| |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "1"); |
| value->SetString(kNameKey, "bad max version"); |
| value->SetString(kMaxVersionKey, "a"); |
| list_with_invalid_format.Append(value); |
| |
| // And finally something that should work. |
| invalidation::ObjectId valid_id(42, "this should work"); |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, "42"); |
| value->SetString(kNameKey, valid_id.name()); |
| value->SetString(kMaxVersionKey, "20"); |
| list_with_invalid_format.Append(value); |
| |
| InvalidatorStorage::DeserializeFromList(list_with_invalid_format, &map); |
| |
| EXPECT_EQ(1U, map.size()); |
| EXPECT_EQ(20, map[valid_id].version); |
| } |
| |
| // Tests behavior when there are duplicate entries for a single key. The value |
| // of the last entry with that key should be used in the version map. |
| TEST_F(InvalidatorStorageTest, DeserializeFromListWithDuplicates) { |
| InvalidationStateMap map; |
| base::ListValue list; |
| DictionaryValue* value; |
| |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kBookmarksId_.source())); |
| value->SetString(kNameKey, kBookmarksId_.name()); |
| value->SetString(kMaxVersionKey, "20"); |
| list.Append(value); |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kAutofillId_.source())); |
| value->SetString(kNameKey, kAutofillId_.name()); |
| value->SetString(kMaxVersionKey, "10"); |
| list.Append(value); |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kBookmarksId_.source())); |
| value->SetString(kNameKey, kBookmarksId_.name()); |
| value->SetString(kMaxVersionKey, "15"); |
| list.Append(value); |
| |
| InvalidatorStorage::DeserializeFromList(list, &map); |
| EXPECT_EQ(2U, map.size()); |
| EXPECT_EQ(10, map[kAutofillId_].version); |
| EXPECT_EQ(15, map[kBookmarksId_].version); |
| } |
| |
| TEST_F(InvalidatorStorageTest, DeserializeFromEmptyList) { |
| InvalidationStateMap map; |
| base::ListValue list; |
| InvalidatorStorage::DeserializeFromList(list, &map); |
| EXPECT_TRUE(map.empty()); |
| } |
| |
| // Tests that deserializing a well-formed value results in the expected state |
| // map. |
| TEST_F(InvalidatorStorageTest, DeserializeFromListBasic) { |
| InvalidationStateMap map; |
| base::ListValue list; |
| DictionaryValue* value; |
| syncer::AckHandle ack_handle_1 = syncer::AckHandle::CreateUnique(); |
| syncer::AckHandle ack_handle_2 = syncer::AckHandle::CreateUnique(); |
| |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, |
| base::IntToString(kAppNotificationsId_.source())); |
| value->SetString(kNameKey, kAppNotificationsId_.name()); |
| value->SetString(kMaxVersionKey, "20"); |
| value->SetString(kPayloadKey, "testing"); |
| value->Set(kCurrentAckHandleKey, ack_handle_1.ToValue().release()); |
| value->Set(kExpectedAckHandleKey, ack_handle_2.ToValue().release()); |
| list.Append(value); |
| |
| InvalidatorStorage::DeserializeFromList(list, &map); |
| EXPECT_EQ(1U, map.size()); |
| EXPECT_EQ(20, map[kAppNotificationsId_].version); |
| EXPECT_EQ("testing", map[kAppNotificationsId_].payload); |
| EXPECT_THAT(map[kAppNotificationsId_].current, Eq(ack_handle_1)); |
| EXPECT_THAT(map[kAppNotificationsId_].expected, Eq(ack_handle_2)); |
| } |
| |
| // Tests that deserializing well-formed values when optional parameters are |
| // omitted works. |
| TEST_F(InvalidatorStorageTest, DeserializeFromListMissingOptionalValues) { |
| InvalidationStateMap map; |
| base::ListValue list; |
| DictionaryValue* value; |
| syncer::AckHandle ack_handle = syncer::AckHandle::CreateUnique(); |
| |
| // Payload missing because of an upgrade from a previous browser version that |
| // didn't set the field. |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kAutofillId_.source())); |
| value->SetString(kNameKey, kAutofillId_.name()); |
| value->SetString(kMaxVersionKey, "10"); |
| list.Append(value); |
| // A crash between SetMaxVersion() and a callback from GenerateAckHandles() |
| // could result in this state. |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kBookmarksId_.source())); |
| value->SetString(kNameKey, kBookmarksId_.name()); |
| value->SetString(kMaxVersionKey, "15"); |
| value->SetString(kPayloadKey, "hello"); |
| list.Append(value); |
| // Never acknowledged, so current ack handle is unset. |
| value = new DictionaryValue(); |
| value->SetString(kSourceKey, base::IntToString(kPreferencesId_.source())); |
| value->SetString(kNameKey, kPreferencesId_.name()); |
| value->SetString(kMaxVersionKey, "20"); |
| value->SetString(kPayloadKey, "world"); |
| value->Set(kExpectedAckHandleKey, ack_handle.ToValue().release()); |
| list.Append(value); |
| |
| InvalidatorStorage::DeserializeFromList(list, &map); |
| EXPECT_EQ(3U, map.size()); |
| |
| EXPECT_EQ(10, map[kAutofillId_].version); |
| EXPECT_EQ("", map[kAutofillId_].payload); |
| EXPECT_FALSE(map[kAutofillId_].current.IsValid()); |
| EXPECT_FALSE(map[kAutofillId_].expected.IsValid()); |
| |
| EXPECT_EQ(15, map[kBookmarksId_].version); |
| EXPECT_EQ("hello", map[kBookmarksId_].payload); |
| EXPECT_FALSE(map[kBookmarksId_].current.IsValid()); |
| EXPECT_FALSE(map[kBookmarksId_].expected.IsValid()); |
| |
| EXPECT_EQ(20, map[kPreferencesId_].version); |
| EXPECT_EQ("world", map[kPreferencesId_].payload); |
| EXPECT_FALSE(map[kPreferencesId_].current.IsValid()); |
| EXPECT_THAT(map[kPreferencesId_].expected, Eq(ack_handle)); |
| } |
| |
| // Tests for legacy deserialization code. |
| TEST_F(InvalidatorStorageTest, DeserializeMapOutOfRange) { |
| InvalidationStateMap map; |
| base::DictionaryValue dict_with_out_of_range_type; |
| |
| dict_with_out_of_range_type.SetString( |
| base::IntToString(syncer::TOP_LEVEL_FOLDER), "100"); |
| dict_with_out_of_range_type.SetString( |
| base::IntToString(syncer::BOOKMARKS), "5"); |
| |
| InvalidatorStorage::DeserializeMap(&dict_with_out_of_range_type, &map); |
| |
| EXPECT_EQ(1U, map.size()); |
| EXPECT_EQ(5, map[kBookmarksId_].version); |
| } |
| |
| TEST_F(InvalidatorStorageTest, DeserializeMapInvalidFormat) { |
| InvalidationStateMap map; |
| base::DictionaryValue dict_with_invalid_format; |
| |
| dict_with_invalid_format.SetString("whoops", "5"); |
| dict_with_invalid_format.SetString("ohnoes", "whoops"); |
| dict_with_invalid_format.SetString( |
| base::IntToString(syncer::BOOKMARKS), "ohnoes"); |
| dict_with_invalid_format.SetString( |
| base::IntToString(syncer::AUTOFILL), "10"); |
| |
| InvalidatorStorage::DeserializeMap(&dict_with_invalid_format, &map); |
| |
| EXPECT_EQ(1U, map.size()); |
| EXPECT_EQ(10, map[kAutofillId_].version); |
| } |
| |
| TEST_F(InvalidatorStorageTest, DeserializeMapEmptyDictionary) { |
| InvalidationStateMap map; |
| base::DictionaryValue dict; |
| InvalidatorStorage::DeserializeMap(&dict, &map); |
| EXPECT_TRUE(map.empty()); |
| } |
| |
| TEST_F(InvalidatorStorageTest, DeserializeMapBasic) { |
| InvalidationStateMap map; |
| base::DictionaryValue dict; |
| |
| dict.SetString(base::IntToString(syncer::AUTOFILL), "10"); |
| dict.SetString(base::IntToString(syncer::BOOKMARKS), "15"); |
| |
| InvalidatorStorage::DeserializeMap(&dict, &map); |
| EXPECT_EQ(2U, map.size()); |
| EXPECT_EQ(10, map[kAutofillId_].version); |
| EXPECT_EQ(15, map[kBookmarksId_].version); |
| } |
| |
| // Test that the migration code for the legacy preference works as expected. |
| // Migration should happen on construction of InvalidatorStorage. |
| TEST_F(InvalidatorStorageTest, MigrateLegacyPreferences) { |
| base::DictionaryValue* legacy_dict = new DictionaryValue; |
| legacy_dict->SetString(base::IntToString(syncer::AUTOFILL), "10"); |
| legacy_dict->SetString(base::IntToString(syncer::BOOKMARKS), "32"); |
| legacy_dict->SetString(base::IntToString(syncer::PREFERENCES), "54"); |
| pref_service_.SetUserPref(prefs::kSyncMaxInvalidationVersions, legacy_dict); |
| InvalidatorStorage storage(&pref_service_); |
| |
| // Legacy pref should be cleared. |
| const base::DictionaryValue* dict = |
| pref_service_.GetDictionary(prefs::kSyncMaxInvalidationVersions); |
| EXPECT_TRUE(dict->empty()); |
| |
| // Validate the new pref is set correctly. |
| InvalidationStateMap map; |
| const base::ListValue* list = |
| pref_service_.GetList(prefs::kInvalidatorMaxInvalidationVersions); |
| InvalidatorStorage::DeserializeFromList(*list, &map); |
| |
| EXPECT_EQ(3U, map.size()); |
| EXPECT_EQ(10, map[kAutofillId_].version); |
| EXPECT_EQ(32, map[kBookmarksId_].version); |
| EXPECT_EQ(54, map[kPreferencesId_].version); |
| } |
| |
| TEST_F(InvalidatorStorageTest, SetGetNotifierClientId) { |
| InvalidatorStorage storage(&pref_service_); |
| const std::string client_id("fK6eDzAIuKqx9A4+93bljg=="); |
| |
| storage.SetInvalidatorClientId(client_id); |
| EXPECT_EQ(client_id, storage.GetInvalidatorClientId()); |
| } |
| |
| TEST_F(InvalidatorStorageTest, SetGetBootstrapData) { |
| InvalidatorStorage storage(&pref_service_); |
| const std::string mess("n\0tK\0\0l\344", 8); |
| ASSERT_FALSE(IsStringUTF8(mess)); |
| |
| storage.SetBootstrapData(mess); |
| EXPECT_EQ(mess, storage.GetBootstrapData()); |
| } |
| |
| // Test that we correctly generate ack handles, acknowledge them, and persist |
| // them. |
| TEST_F(InvalidatorStorageTest, GenerateAckHandlesAndAcknowledge) { |
| InvalidatorStorage storage(&pref_service_); |
| syncer::ObjectIdSet ids; |
| InvalidationStateMap state_map; |
| syncer::AckHandleMap ack_handle_map; |
| syncer::AckHandleMap::const_iterator it; |
| |
| // Test that it works as expected if the key doesn't already exist in the map, |
| // e.g. the first invalidation received for the object ID was not for a |
| // specific version. |
| ids.insert(kAutofillId_); |
| storage.GenerateAckHandles( |
| ids, base::MessageLoopProxy::current(), |
| base::Bind(&GenerateAckHandlesTestHelper, &ack_handle_map)); |
| loop_.RunUntilIdle(); |
| EXPECT_EQ(1U, ack_handle_map.size()); |
| it = ack_handle_map.find(kAutofillId_); |
| // Android STL appears to be buggy and causes gtest's IsContainerTest<> to |
| // treat an iterator as a STL container so we use != instead of ASSERT_NE. |
| ASSERT_TRUE(ack_handle_map.end() != it); |
| EXPECT_TRUE(it->second.IsValid()); |
| state_map[kAutofillId_].expected = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| |
| storage.Acknowledge(kAutofillId_, it->second); |
| state_map[kAutofillId_].current = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| |
| ids.clear(); |
| |
| // Test that it works as expected if the key already exists. |
| state_map[kBookmarksId_].version = 11; |
| state_map[kBookmarksId_].payload = "hello"; |
| storage.SetMaxVersionAndPayload(kBookmarksId_, 11, "hello"); |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| ids.insert(kBookmarksId_); |
| storage.GenerateAckHandles( |
| ids, base::MessageLoopProxy::current(), |
| base::Bind(&GenerateAckHandlesTestHelper, &ack_handle_map)); |
| loop_.RunUntilIdle(); |
| EXPECT_EQ(1U, ack_handle_map.size()); |
| it = ack_handle_map.find(kBookmarksId_); |
| ASSERT_TRUE(ack_handle_map.end() != it); |
| EXPECT_TRUE(it->second.IsValid()); |
| state_map[kBookmarksId_].expected = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| |
| storage.Acknowledge(kBookmarksId_, it->second); |
| state_map[kBookmarksId_].current = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| |
| // Finally, test that the ack handles are updated if we're asked to generate |
| // another ack handle for the same object ID. |
| state_map[kBookmarksId_].version = 12; |
| state_map[kBookmarksId_].payload = "world"; |
| storage.SetMaxVersionAndPayload(kBookmarksId_, 12, "world"); |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| ids.insert(kBookmarksId_); |
| storage.GenerateAckHandles( |
| ids, base::MessageLoopProxy::current(), |
| base::Bind(&GenerateAckHandlesTestHelper, &ack_handle_map)); |
| loop_.RunUntilIdle(); |
| EXPECT_EQ(1U, ack_handle_map.size()); |
| it = ack_handle_map.find(kBookmarksId_); |
| ASSERT_TRUE(ack_handle_map.end() != it); |
| EXPECT_TRUE(it->second.IsValid()); |
| state_map[kBookmarksId_].expected = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| |
| storage.Acknowledge(kBookmarksId_, it->second); |
| state_map[kBookmarksId_].current = it->second; |
| EXPECT_EQ(state_map, storage.GetAllInvalidationStates()); |
| } |
| |
| } // namespace invalidation |