// Copyright 2013 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 "sync/engine/download.h" #include "base/message_loop/message_loop.h" #include "base/stl_util.h" #include "sync/engine/sync_directory_update_handler.h" #include "sync/internal_api/public/base/model_type_test_util.h" #include "sync/protocol/sync.pb.h" #include "sync/sessions/debug_info_getter.h" #include "sync/sessions/nudge_tracker.h" #include "sync/sessions/status_controller.h" #include "sync/syncable/directory.h" #include "sync/test/engine/fake_model_worker.h" #include "sync/test/engine/test_directory_setter_upper.h" #include "sync/test/sessions/mock_debug_info_getter.h" #include "testing/gtest/include/gtest/gtest.h" namespace syncer { using sessions::MockDebugInfoGetter; // A test fixture for tests exercising download updates functions. class DownloadUpdatesTest : public ::testing::Test { protected: DownloadUpdatesTest() : update_handler_map_deleter_(&update_handler_map_) { } virtual void SetUp() { dir_maker_.SetUp(); AddUpdateHandler(AUTOFILL, GROUP_DB); AddUpdateHandler(BOOKMARKS, GROUP_UI); AddUpdateHandler(PREFERENCES, GROUP_UI); } virtual void TearDown() { dir_maker_.TearDown(); } ModelTypeSet proto_request_types() { ModelTypeSet types; for (UpdateHandlerMap::iterator it = update_handler_map_.begin(); it != update_handler_map_.end(); ++it) { types.Put(it->first); } return types; } syncable::Directory* directory() { return dir_maker_.directory(); } UpdateHandlerMap* update_handler_map() { return &update_handler_map_; } void InitFakeUpdateResponse(sync_pb::GetUpdatesResponse* response) { ModelTypeSet types = proto_request_types(); for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) { sync_pb::DataTypeProgressMarker* marker = response->add_new_progress_marker(); marker->set_data_type_id(GetSpecificsFieldNumberFromModelType(it.Get())); marker->set_token("foobarbaz"); } response->set_changes_remaining(0); } private: void AddUpdateHandler(ModelType type, ModelSafeGroup group) { DCHECK(directory()); scoped_refptr worker = new FakeModelWorker(group); SyncDirectoryUpdateHandler* handler = new SyncDirectoryUpdateHandler(directory(), type, worker); update_handler_map_.insert(std::make_pair(type, handler)); } base::MessageLoop loop_; // Needed for directory init. TestDirectorySetterUpper dir_maker_; UpdateHandlerMap update_handler_map_; STLValueDeleter update_handler_map_deleter_; DISALLOW_COPY_AND_ASSIGN(DownloadUpdatesTest); }; // Basic test to make sure nudges are expressed properly in the request. TEST_F(DownloadUpdatesTest, BookmarkNudge) { sessions::NudgeTracker nudge_tracker; nudge_tracker.RecordLocalChange(ModelTypeSet(BOOKMARKS)); sync_pb::ClientToServerMessage msg; download::BuildNormalDownloadUpdatesImpl(proto_request_types(), update_handler_map(), nudge_tracker, msg.mutable_get_updates()); const sync_pb::GetUpdatesMessage& gu_msg = msg.get_updates(); EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::LOCAL, gu_msg.caller_info().source()); EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin()); for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) { syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber( gu_msg.from_progress_marker(i).data_type_id()); const sync_pb::DataTypeProgressMarker& progress_marker = gu_msg.from_progress_marker(i); const sync_pb::GetUpdateTriggers& gu_trigger = progress_marker.get_update_triggers(); // We perform some basic tests of GU trigger and source fields here. The // more complicated scenarios are tested by the NudgeTracker tests. if (type == BOOKMARKS) { EXPECT_TRUE(progress_marker.has_notification_hint()); EXPECT_EQ("", progress_marker.notification_hint()); EXPECT_EQ(1, gu_trigger.local_modification_nudges()); EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges()); } else { EXPECT_FALSE(progress_marker.has_notification_hint()); EXPECT_EQ(0, gu_trigger.local_modification_nudges()); EXPECT_EQ(0, gu_trigger.datatype_refresh_nudges()); } } } // Basic test to ensure invalidation payloads are expressed in the request. TEST_F(DownloadUpdatesTest, NotifyMany) { sessions::NudgeTracker nudge_tracker; nudge_tracker.RecordRemoteInvalidation( BuildInvalidationMap(AUTOFILL, 1, "autofill_payload")); nudge_tracker.RecordRemoteInvalidation( BuildInvalidationMap(BOOKMARKS, 1, "bookmark_payload")); nudge_tracker.RecordRemoteInvalidation( BuildInvalidationMap(PREFERENCES, 1, "preferences_payload")); ModelTypeSet notified_types; notified_types.Put(AUTOFILL); notified_types.Put(BOOKMARKS); notified_types.Put(PREFERENCES); sync_pb::ClientToServerMessage msg; download::BuildNormalDownloadUpdatesImpl(proto_request_types(), update_handler_map(), nudge_tracker, msg.mutable_get_updates()); const sync_pb::GetUpdatesMessage& gu_msg = msg.get_updates(); EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::NOTIFICATION, gu_msg.caller_info().source()); EXPECT_EQ(sync_pb::SyncEnums::GU_TRIGGER, gu_msg.get_updates_origin()); for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) { syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber( gu_msg.from_progress_marker(i).data_type_id()); const sync_pb::DataTypeProgressMarker& progress_marker = gu_msg.from_progress_marker(i); const sync_pb::GetUpdateTriggers& gu_trigger = progress_marker.get_update_triggers(); // We perform some basic tests of GU trigger and source fields here. The // more complicated scenarios are tested by the NudgeTracker tests. if (notified_types.Has(type)) { EXPECT_TRUE(progress_marker.has_notification_hint()); EXPECT_FALSE(progress_marker.notification_hint().empty()); EXPECT_EQ(1, gu_trigger.notification_hint_size()); } else { EXPECT_FALSE(progress_marker.has_notification_hint()); EXPECT_EQ(0, gu_trigger.notification_hint_size()); } } } TEST_F(DownloadUpdatesTest, ConfigureTest) { sync_pb::ClientToServerMessage msg; download::BuildDownloadUpdatesForConfigureImpl( proto_request_types(), update_handler_map(), sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, msg.mutable_get_updates()); const sync_pb::GetUpdatesMessage& gu_msg = msg.get_updates(); EXPECT_EQ(sync_pb::SyncEnums::RECONFIGURATION, gu_msg.get_updates_origin()); EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::RECONFIGURATION, gu_msg.caller_info().source()); ModelTypeSet progress_types; for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) { syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber( gu_msg.from_progress_marker(i).data_type_id()); progress_types.Put(type); } EXPECT_TRUE(proto_request_types().Equals(progress_types)); } TEST_F(DownloadUpdatesTest, PollTest) { sync_pb::ClientToServerMessage msg; download::BuildDownloadUpdatesForPollImpl( proto_request_types(), update_handler_map(), msg.mutable_get_updates()); const sync_pb::GetUpdatesMessage& gu_msg = msg.get_updates(); EXPECT_EQ(sync_pb::SyncEnums::PERIODIC, gu_msg.get_updates_origin()); EXPECT_EQ(sync_pb::GetUpdatesCallerInfo::PERIODIC, gu_msg.caller_info().source()); ModelTypeSet progress_types; for (int i = 0; i < gu_msg.from_progress_marker_size(); ++i) { syncer::ModelType type = GetModelTypeFromSpecificsFieldNumber( gu_msg.from_progress_marker(i).data_type_id()); progress_types.Put(type); } EXPECT_TRUE(proto_request_types().Equals(progress_types)); } // Verify that a bogus response message is detected. TEST_F(DownloadUpdatesTest, InvalidResponse) { sync_pb::GetUpdatesResponse gu_response; InitFakeUpdateResponse(&gu_response); // This field is essential for making the client stop looping. If it's unset // then something is very wrong. The client should detect this. gu_response.clear_changes_remaining(); sessions::StatusController status; SyncerError error = download::ProcessResponse(gu_response, proto_request_types(), update_handler_map(), &status); EXPECT_EQ(error, SERVER_RESPONSE_VALIDATION_FAILED); } // Verify that we correctly detect when there's more work to be done. TEST_F(DownloadUpdatesTest, MoreToDownloadResponse) { sync_pb::GetUpdatesResponse gu_response; InitFakeUpdateResponse(&gu_response); gu_response.set_changes_remaining(1); sessions::StatusController status; SyncerError error = download::ProcessResponse(gu_response, proto_request_types(), update_handler_map(), &status); EXPECT_EQ(error, SERVER_MORE_TO_DOWNLOAD); } // A simple scenario: No updates returned and nothing more to download. TEST_F(DownloadUpdatesTest, NormalResponseTest) { sync_pb::GetUpdatesResponse gu_response; InitFakeUpdateResponse(&gu_response); gu_response.set_changes_remaining(0); sessions::StatusController status; SyncerError error = download::ProcessResponse(gu_response, proto_request_types(), update_handler_map(), &status); EXPECT_EQ(error, SYNCER_OK); } class DownloadUpdatesDebugInfoTest : public ::testing::Test { public: DownloadUpdatesDebugInfoTest() {} virtual ~DownloadUpdatesDebugInfoTest() {} sessions::StatusController* status() { return &status_; } sessions::DebugInfoGetter* debug_info_getter() { return &debug_info_getter_; } void AddDebugEvent() { debug_info_getter_.AddDebugEvent(); } private: sessions::StatusController status_; MockDebugInfoGetter debug_info_getter_; }; // Verify CopyClientDebugInfo when there are no events to upload. TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyClientDebugInfo_Empty) { sync_pb::DebugInfo debug_info; download::CopyClientDebugInfo(debug_info_getter(), &debug_info); EXPECT_EQ(0, debug_info.events_size()); } TEST_F(DownloadUpdatesDebugInfoTest, VerifyCopyOverwrites) { sync_pb::DebugInfo debug_info; AddDebugEvent(); download::CopyClientDebugInfo(debug_info_getter(), &debug_info); EXPECT_EQ(1, debug_info.events_size()); download::CopyClientDebugInfo(debug_info_getter(), &debug_info); EXPECT_EQ(1, debug_info.events_size()); } } // namespace syncer