Index: chrome/browser/notifications/sync_notifier/chrome_notifier_service_unittest.cc |
diff --git a/chrome/browser/notifications/sync_notifier/chrome_notifier_service_unittest.cc b/chrome/browser/notifications/sync_notifier/chrome_notifier_service_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dc6a64f09ebe14e172306bd0a95d5afa36a573dd |
--- /dev/null |
+++ b/chrome/browser/notifications/sync_notifier/chrome_notifier_service_unittest.cc |
@@ -0,0 +1,389 @@ |
+// 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 <map> |
+ |
+#include "base/memory/scoped_ptr.h" |
+#include "chrome/browser/notifications/notification_ui_manager.h" |
+#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h" |
+#include "chrome/browser/notifications/sync_notifier/synced_notification.h" |
+#include "sync/api/sync_change.h" |
+#include "sync/api/sync_change_processor.h" |
+#include "sync/api/sync_error_factory.h" |
+#include "sync/api/sync_error_factory_mock.h" |
+#include "sync/protocol/sync.pb.h" |
+#include "sync/protocol/synced_notification_specifics.pb.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using sync_pb::SyncedNotificationSpecifics; |
+using sync_pb::EntitySpecifics; |
+using sync_pb::SyncedNotificationRenderInfo_Layout_LayoutType_TITLE_AND_SUBTEXT; |
+using syncer::SyncData; |
+using syncer::SyncChange; |
+using syncer::SyncChangeList; |
+using syncer::SyncDataList; |
+using syncer::SYNCED_NOTIFICATIONS; |
+using notifier::SyncedNotification; |
+using notifier::ChromeNotifierService; |
+ |
+namespace { |
+ |
+const char kAppId1[] = "fboilmbenheemaomgaeehigklolhkhnf"; |
+const char kAppId2[] = "fbcmoldooppoahjhfflnmljoanccekpf"; |
+const char kAppId3[] = "fbcmoldooppoahjhfflnmljoanccek33"; |
+const char kAppId4[] = "fbcmoldooppoahjhfflnmljoanccek44"; |
+const char kAppId5[] = "fbcmoldooppoahjhfflnmljoanccek55"; |
+const char kAppId6[] = "fbcmoldooppoahjhfflnmljoanccek66"; |
+const char kAppId7[] = "fbcmoldooppoahjhfflnmljoanccek77"; |
+const char kCoalescingKey1[] = "foo"; |
+const char kCoalescingKey2[] = "bar"; |
+const char kCoalescingKey3[] = "bat"; |
+const char kCoalescingKey4[] = "baz"; |
+const char kCoalescingKey5[] = "foobar"; |
+const char kCoalescingKey6[] = "fu"; |
+const char kCoalescingKey7[] = "meta"; |
+const char kNotificationId1[] = "fboilmbenheemaomgaeehigklolhkhnf/foo"; |
+const char kNotificationId2[] = "fbcmoldooppoahjhfflnmljoanccekpf/bar"; |
+const char kNotificationId3[] = "fbcmoldooppoahjhfflnmljoanccek33/bat"; |
+const char kNotificationId4[] = "fbcmoldooppoahjhfflnmljoanccek44/baz"; |
+const char kNotificationId5[] = "fbcmoldooppoahjhfflnmljoanccek55/foobar"; |
+const char kNotificationId6[] = "fbcmoldooppoahjhfflnmljoanccek66/fu"; |
+const char kNotificationId7[] = "fbcmoldooppoahjhfflnmljoanccek77/meta"; |
+ |
+const int64 kFakeCreationTime = 42; |
+ |
+// Extract notification id from syncer::SyncData. |
+std::string GetNotificationId(const SyncData& sync_data) { |
+ SyncedNotificationSpecifics specifics = sync_data.GetSpecifics(). |
+ synced_notification(); |
+ std::string notification_id = specifics. |
+ coalesced_notification().id().app_id(); |
+ notification_id += "/"; |
+ notification_id += specifics.coalesced_notification().id().coalescing_key(); |
+ return notification_id; |
+} |
+ |
+// Stub out the NotificationUIManager for unit testing. |
+class StubNotificationUIManager : public NotificationUIManager { |
+ public: |
+ StubNotificationUIManager() {} |
+ virtual ~StubNotificationUIManager() {} |
+ |
+ // Adds a notification to be displayed. Virtual for unit test override. |
+ virtual void Add(const Notification& notification, Profile* profile) |
+ OVERRIDE {} |
+ |
+ // Returns true if any notifications match the supplied ID, either currently |
+ // displayed or in the queue. |
+ virtual bool DoesIdExist(const std::string& id) { |
+ return true; |
+ } |
+ |
+ // Removes any notifications matching the supplied ID, either currently |
+ // displayed or in the queue. Returns true if anything was removed. |
+ virtual bool CancelById(const std::string& notification_id) OVERRIDE { |
+ return false; |
+ } |
+ |
+ // Removes notifications matching the |source_origin| (which could be an |
+ // extension ID). Returns true if anything was removed. |
+ virtual bool CancelAllBySourceOrigin(const GURL& source_origin) OVERRIDE { |
+ return false; |
+ } |
+ |
+ // Removes notifications matching |profile|. Returns true if any were removed. |
+ virtual bool CancelAllByProfile(Profile* profile) OVERRIDE { |
+ return false; |
+ } |
+ |
+ // Cancels all pending notifications and closes anything currently showing. |
+ // Used when the app is terminating. |
+ virtual void CancelAll() OVERRIDE {} |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(StubNotificationUIManager); |
+}; |
+ |
+// Dummy SyncChangeProcessor used to help review what SyncChanges are pushed |
+// back up to Sync. |
+class TestChangeProcessor : public syncer::SyncChangeProcessor { |
+ public: |
+ TestChangeProcessor() { } |
+ virtual ~TestChangeProcessor() { } |
+ |
+ // Store a copy of all the changes passed in so we can examine them later. |
+ virtual syncer::SyncError ProcessSyncChanges( |
+ const tracked_objects::Location& from_here, |
+ const SyncChangeList& change_list) OVERRIDE { |
+ change_map_.clear(); |
+ for (SyncChangeList::const_iterator iter = change_list.begin(); |
+ iter != change_list.end(); ++iter) { |
+ // Put the data into the change tracking map. |
+ change_map_[GetNotificationId(iter->sync_data())] = *iter; |
+ } |
+ |
+ return syncer::SyncError(); |
+ } |
+ |
+ size_t change_list_size() { return change_map_.size(); } |
+ |
+ bool ContainsId(const std::string& id) { |
+ return change_map_.find(id) != change_map_.end(); |
+ } |
+ |
+ SyncChange GetChangeById(const std::string& id) { |
+ EXPECT_TRUE(ContainsId(id)); |
+ return change_map_[id]; |
+ } |
+ |
+ private: |
+ // Track the changes received in ProcessSyncChanges. |
+ std::map<std::string, SyncChange> change_map_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestChangeProcessor); |
+}; |
+ |
+class SyncChangeProcessorDelegate : public syncer::SyncChangeProcessor { |
+ public: |
+ explicit SyncChangeProcessorDelegate(SyncChangeProcessor* recipient) |
+ : recipient_(recipient) { |
+ EXPECT_TRUE(recipient_); |
+ } |
+ virtual ~SyncChangeProcessorDelegate() {} |
+ |
+ // syncer::SyncChangeProcessor implementation. |
+ virtual syncer::SyncError ProcessSyncChanges( |
+ const tracked_objects::Location& from_here, |
+ const SyncChangeList& change_list) OVERRIDE { |
+ return recipient_->ProcessSyncChanges(from_here, change_list); |
+ } |
+ |
+ private: |
+ // The recipient of all sync changes. |
+ SyncChangeProcessor* recipient_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SyncChangeProcessorDelegate); |
+}; |
+ |
+} // namespace |
+ |
+class ChromeNotifierServiceTest : public testing::Test { |
+ public: |
+ ChromeNotifierServiceTest() |
+ : sync_processor_(new TestChangeProcessor), |
+ sync_processor_delegate_(new SyncChangeProcessorDelegate( |
+ sync_processor_.get())) {} |
+ ~ChromeNotifierServiceTest() {} |
+ |
+ // Methods from testing::Test. |
+ virtual void SetUp() {} |
+ virtual void TearDown() {} |
+ |
+ TestChangeProcessor* processor() { |
+ return static_cast<TestChangeProcessor*>(sync_processor_.get()); |
+} |
+ |
+ scoped_ptr<syncer::SyncChangeProcessor> PassProcessor() { |
+ return sync_processor_delegate_.Pass(); |
+ } |
+ |
+ SyncedNotification* CreateNotification(const std::string& message, |
+ const std::string& app_id, |
+ const std::string& coalescing_key, |
+ const std::string& external_id) { |
+ SyncData sync_data = CreateSyncData(message, app_id, coalescing_key, |
+ external_id); |
+ // Set enough fields in sync_data, including specifics, for our tests |
+ // to pass. |
+ return new SyncedNotification(sync_data); |
+ } |
+ |
+ // Helper to create syncer::SyncChange. |
+ static SyncChange CreateSyncChange( |
+ SyncChange::SyncChangeType type, |
+ SyncedNotification* notification) { |
+ // Take control of the notification to clean it up after we create data |
+ // out of it. |
+ scoped_ptr<SyncedNotification> scoped_notification(notification); |
+ return SyncChange( |
+ FROM_HERE, |
+ type, |
+ ChromeNotifierService::CreateSyncDataFromNotification(*notification)); |
+ } |
+ |
+ // Helper to create syncer::SyncData. |
+ static SyncData CreateSyncData(const std::string& message, |
+ const std::string& app_id, |
+ const std::string& coalescing_key, |
+ const std::string& external_id) { |
+ // CreateLocalData makes a copy of this, so this can safely live |
+ // on the stack. |
+ EntitySpecifics entity_specifics; |
+ |
+ SyncedNotificationSpecifics* specifics = |
+ entity_specifics.mutable_synced_notification(); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ mutable_render_info()-> |
+ mutable_layout()-> |
+ set_layout_type( |
+ SyncedNotificationRenderInfo_Layout_LayoutType_TITLE_AND_SUBTEXT); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ mutable_id()-> |
+ set_app_id(app_id); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ mutable_id()-> |
+ set_coalescing_key(coalescing_key); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ mutable_render_info()-> |
+ mutable_layout()-> |
+ mutable_title_and_subtext_data()-> |
+ set_title(message); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ set_creation_time_msec(kFakeCreationTime); |
+ |
+ specifics->mutable_coalesced_notification()-> |
+ add_notification(); |
+ specifics->mutable_coalesced_notification()-> |
+ mutable_notification(0)->set_external_id(external_id); |
+ |
+ SyncData sync_data = SyncData::CreateLocalData( |
+ "syncer::SYNCED_NOTIFICATIONS", |
+ "ChromeNotifierServiceUnitTest", |
+ entity_specifics); |
+ |
+ return sync_data; |
+ } |
+ |
+ private: |
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; |
+ scoped_ptr<syncer::SyncChangeProcessor> sync_processor_delegate_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(ChromeNotifierServiceTest); |
+}; |
+ |
+// Create a Notification, convert it to SyncData and convert it back. |
+TEST_F(ChromeNotifierServiceTest, NotificationToSyncDataToNotification) { |
+ // TODO(petewil): Add more properties to this test. |
+ scoped_ptr<SyncedNotification> notification1( |
+ CreateNotification("1", kAppId1, kCoalescingKey1, "11")); |
+ SyncData sync_data = |
+ ChromeNotifierService::CreateSyncDataFromNotification(*notification1); |
+ scoped_ptr<SyncedNotification> notification2( |
+ ChromeNotifierService::CreateNotificationFromSyncData(sync_data)); |
+ EXPECT_TRUE(notification2.get()); |
+ EXPECT_TRUE(notification1->Equals(*notification2)); |
+} |
+ |
+// Model assocation: We have no local data, and no remote data. |
+TEST_F(ChromeNotifierServiceTest, ModelAssocBothEmpty) { |
+ StubNotificationUIManager notification_manager; |
+ ChromeNotifierService notifier(NULL, ¬ification_manager); |
+ |
+ notifier.MergeDataAndStartSyncing( |
+ SYNCED_NOTIFICATIONS, |
+ SyncDataList(), // Empty. |
+ PassProcessor(), |
+ scoped_ptr<syncer::SyncErrorFactory>(new syncer::SyncErrorFactoryMock())); |
+ |
+ EXPECT_EQ(0U, notifier.GetAllSyncData(SYNCED_NOTIFICATIONS).size()); |
+ EXPECT_EQ(0U, processor()->change_list_size()); |
+} |
+ |
+// Process sync changes when there is no local data. |
+TEST_F(ChromeNotifierServiceTest, ProcessSyncChangesEmptyModel) { |
+ // We initially have no data. |
+ StubNotificationUIManager notification_manager; |
+ ChromeNotifierService notifier(NULL, ¬ification_manager); |
+ |
+ notifier.MergeDataAndStartSyncing( |
+ SYNCED_NOTIFICATIONS, |
+ SyncDataList(), |
+ PassProcessor(), |
+ scoped_ptr<syncer::SyncErrorFactory>(new syncer::SyncErrorFactoryMock())); |
+ |
+ // Set up a bunch of ADDs. |
+ SyncChangeList changes; |
+ changes.push_back(CreateSyncChange( |
+ SyncChange::ACTION_ADD, CreateNotification( |
+ "1", kAppId1, kCoalescingKey1, "11"))); |
+ changes.push_back(CreateSyncChange( |
+ SyncChange::ACTION_ADD, CreateNotification( |
+ "2", kAppId2, kCoalescingKey2, "22"))); |
+ changes.push_back(CreateSyncChange( |
+ SyncChange::ACTION_ADD, CreateNotification( |
+ "3", kAppId3, kCoalescingKey3, "33"))); |
+ |
+ notifier.ProcessSyncChanges(FROM_HERE, changes); |
+ |
+ EXPECT_EQ(3U, notifier.GetAllSyncData(SYNCED_NOTIFICATIONS).size()); |
+ // TODO(petewil): verify that the list entries have expected values to make |
+ // this test more robust. |
+} |
+ |
+// Model has some notifications, some of them are local only. Sync has some |
+// notifications. No items match up. |
+TEST_F(ChromeNotifierServiceTest, LocalRemoteBothNonEmptyNoOverlap) { |
+ StubNotificationUIManager notification_manager; |
+ ChromeNotifierService notifier(NULL, ¬ification_manager); |
+ |
+ // Create some local fake data. |
+ scoped_ptr<SyncedNotification> n1(CreateNotification( |
+ "1", kAppId1, kCoalescingKey1, "11")); |
+ notifier.AddForTest(n1.Pass()); |
+ scoped_ptr<SyncedNotification> n2(CreateNotification( |
+ "2", kAppId2, kCoalescingKey2, "22")); |
+ notifier.AddForTest(n2.Pass()); |
+ scoped_ptr<SyncedNotification> n3(CreateNotification( |
+ "3", kAppId3, kCoalescingKey3, "33")); |
+ notifier.AddForTest(n3.Pass()); |
+ |
+ // Create some remote fake data. |
+ SyncDataList initial_data; |
+ initial_data.push_back(CreateSyncData("4", kAppId4, kCoalescingKey4, "44")); |
+ initial_data.push_back(CreateSyncData("5", kAppId5, kCoalescingKey5, "55")); |
+ initial_data.push_back(CreateSyncData("6", kAppId6, kCoalescingKey6, "66")); |
+ initial_data.push_back(CreateSyncData("7", kAppId7, kCoalescingKey7, "77")); |
+ |
+ // Merge the local and remote data. |
+ notifier.MergeDataAndStartSyncing( |
+ SYNCED_NOTIFICATIONS, |
+ initial_data, |
+ PassProcessor(), |
+ scoped_ptr<syncer::SyncErrorFactory>(new syncer::SyncErrorFactoryMock())); |
+ |
+ // Ensure the local store now has all local and remote notifications. |
+ EXPECT_EQ(7U, notifier.GetAllSyncData(SYNCED_NOTIFICATIONS).size()); |
+ for (SyncDataList::const_iterator iter = initial_data.begin(); |
+ iter != initial_data.end(); ++iter) { |
+ scoped_ptr<SyncedNotification> notification1( |
+ ChromeNotifierService::CreateNotificationFromSyncData(*iter)); |
+ // TODO(petewil): Revisit this when we add version info to notifications. |
+ const std::string& id = notification1->notification_id(); |
+ const SyncedNotification* notification2 = notifier.FindNotificationById(id); |
+ EXPECT_TRUE(NULL != notification2); |
+ EXPECT_TRUE(notification1->Equals(*notification2)); |
+ } |
+ EXPECT_TRUE(notifier.FindNotificationById(kNotificationId1)); |
+ EXPECT_TRUE(notifier.FindNotificationById(kNotificationId2)); |
+ EXPECT_TRUE(notifier.FindNotificationById(kNotificationId3)); |
+ |
+ // Verify the changes made it up to the remote service. |
+ EXPECT_EQ(3U, processor()->change_list_size()); |
+ EXPECT_TRUE(processor()->ContainsId(kNotificationId1)); |
+ EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeById( |
+ kNotificationId1).change_type()); |
+ EXPECT_FALSE(processor()->ContainsId(kNotificationId4)); |
+ EXPECT_TRUE(processor()->ContainsId(kNotificationId3)); |
+ EXPECT_EQ(SyncChange::ACTION_ADD, processor()->GetChangeById( |
+ kNotificationId3).change_type()); |
+} |
+ |
+// TODO(petewil): There are more tests to add, such as when an item in |
+// the local store matches up with one from the server, with and without |
+// merge conflicts. |