1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/synced_notifications_private/synced_notifications_shim.h"
6
7#include "base/json/json_writer.h"
8#include "extensions/browser/event_router.h"
9#include "sync/api/fake_sync_change_processor.h"
10#include "sync/api/sync_change.h"
11#include "sync/api/sync_error_factory.h"
12#include "sync/protocol/sync.pb.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15using namespace extensions;
16using namespace extensions::api;
17
18namespace {
19
20// Builds a SyncData for the specified |type| based on |key|.
21syncer::SyncData BuildData(syncer::ModelType type, const std::string& key) {
22  sync_pb::EntitySpecifics specifics;
23  if (type == syncer::SYNCED_NOTIFICATIONS) {
24    specifics.mutable_synced_notification()
25        ->mutable_coalesced_notification()
26        ->set_key(key);
27  } else {
28    specifics.mutable_synced_notification_app_info()
29        ->add_synced_notification_app_info()
30        ->add_app_id(key);
31  }
32  syncer::SyncData data = syncer::SyncData::CreateLocalData(
33      key, key, specifics);
34  return data;
35}
36
37// Builds a SyncChange with an update to the specified |type| based on |key|.
38syncer::SyncChange BuildChange(syncer::ModelType type, const std::string& key) {
39  syncer::SyncChangeList change_list;
40  syncer::SyncData data = BuildData(type, key);
41  return syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data);
42}
43
44// Verifies that the specifics within |data| match the serialized specifics
45// within |serialized|.
46testing::AssertionResult DataSpecificsMatch(const syncer::SyncData& data,
47                                            const std::string& serialized) {
48  if (data.GetDataType() == syncer::SYNCED_NOTIFICATIONS) {
49    const sync_pb::SyncedNotificationSpecifics& proto =
50        data.GetSpecifics().synced_notification();
51    if (serialized != proto.SerializeAsString())
52      return testing::AssertionFailure() << "Notification specifics mismatch";
53  } else {
54    const sync_pb::SyncedNotificationAppInfoSpecifics& proto =
55        data.GetSpecifics().synced_notification_app_info();
56    if (serialized != proto.SerializeAsString())
57      return testing::AssertionFailure() << "App info specifics mismatch";
58  }
59  return testing::AssertionSuccess();
60}
61
62// Verifies that the update within |change| matchs the serialized specifics
63// within |serialized|.
64testing::AssertionResult ChangeSpecificsMatch(const syncer::SyncChange& change,
65                                              const std::string& serialized) {
66  if (change.change_type() != syncer::SyncChange::ACTION_UPDATE)
67    return testing::AssertionFailure() << "Invalid change type";
68  return DataSpecificsMatch(change.sync_data(), serialized);
69}
70
71class SyncedNotificationsShimTest : public testing::Test {
72 public:
73  SyncedNotificationsShimTest();
74  virtual ~SyncedNotificationsShimTest();
75
76  // Starts sync for both sync types.
77  void StartSync();
78  // Starts sync for the specified datatype |type|.
79  void StartSync(syncer::ModelType type);
80
81  // Transfers ownership of the last event received.
82  scoped_ptr<Event> GetLastEvent();
83
84  // Records that a refresh has been requested.
85  void RequestRefresh();
86
87  SyncedNotificationsShim* shim() { return &shim_; }
88  syncer::FakeSyncChangeProcessor* notification_processor() {
89    return notification_processor_;
90  }
91  syncer::FakeSyncChangeProcessor* app_info_processor() {
92    return app_info_processor_;
93  }
94  bool refresh_requested() const { return refresh_requested_; }
95
96 private:
97  void EventCallback(scoped_ptr<Event> event);
98
99  // Shim being tested.
100  SyncedNotificationsShim shim_;
101
102  syncer::FakeSyncChangeProcessor* notification_processor_;
103  syncer::FakeSyncChangeProcessor* app_info_processor_;
104
105  // The last event fired by the shim.
106  scoped_ptr<Event> last_event_fired_;
107
108  // Whether a refresh has been requested;
109  bool refresh_requested_;
110};
111
112SyncedNotificationsShimTest::SyncedNotificationsShimTest()
113    : shim_(base::Bind(&SyncedNotificationsShimTest::EventCallback,
114                       base::Unretained(this)),
115            base::Bind(&SyncedNotificationsShimTest::RequestRefresh,
116                       base::Unretained(this))),
117      notification_processor_(NULL),
118      app_info_processor_(NULL),
119      refresh_requested_(false) {}
120
121SyncedNotificationsShimTest::~SyncedNotificationsShimTest() {}
122
123void SyncedNotificationsShimTest::EventCallback(scoped_ptr<Event> event) {
124  ASSERT_FALSE(last_event_fired_);
125  last_event_fired_ = event.Pass();
126}
127
128void SyncedNotificationsShimTest::RequestRefresh() {
129  ASSERT_FALSE(refresh_requested_);
130  refresh_requested_ = true;
131}
132
133scoped_ptr<Event> SyncedNotificationsShimTest::GetLastEvent() {
134  return last_event_fired_.Pass();
135}
136
137void SyncedNotificationsShimTest::StartSync() {
138  StartSync(syncer::SYNCED_NOTIFICATIONS);
139  StartSync(syncer::SYNCED_NOTIFICATION_APP_INFO);
140  GetLastEvent();
141}
142
143void SyncedNotificationsShimTest::StartSync(syncer::ModelType type) {
144  scoped_ptr<syncer::FakeSyncChangeProcessor> change_processor(
145      new syncer::FakeSyncChangeProcessor());
146  if (type == syncer::SYNCED_NOTIFICATIONS)
147    notification_processor_ = change_processor.get();
148  else
149    app_info_processor_ = change_processor.get();
150  syncer::SyncDataList sync_data;
151  shim_.MergeDataAndStartSyncing(
152      type,
153      sync_data,
154      change_processor.PassAs<syncer::SyncChangeProcessor>(),
155      scoped_ptr<syncer::SyncErrorFactory>());
156}
157
158// Starting sync should fire the sync started event, but only after both types
159// have started.
160TEST_F(SyncedNotificationsShimTest, StartSync) {
161  EXPECT_FALSE(shim()->IsSyncReady());
162  StartSync(syncer::SYNCED_NOTIFICATIONS);
163  EXPECT_FALSE(shim()->IsSyncReady());
164  EXPECT_FALSE(GetLastEvent());
165
166  StartSync(syncer::SYNCED_NOTIFICATION_APP_INFO);
167  EXPECT_TRUE(shim()->IsSyncReady());
168
169  scoped_ptr<Event> event = GetLastEvent();
170  ASSERT_TRUE(event);
171  EXPECT_EQ(synced_notifications_private::OnSyncStartup::kEventName,
172            event->event_name);
173
174  EXPECT_TRUE(notification_processor()->changes().empty());
175  EXPECT_TRUE(app_info_processor()->changes().empty());
176}
177
178// A sync update should fire the OnDataChanges event with the updated
179// notification.
180TEST_F(SyncedNotificationsShimTest, ProcessSyncChangesSingleNotification) {
181  StartSync();
182  syncer::SyncChangeList change_list;
183  change_list.push_back(BuildChange(syncer::SYNCED_NOTIFICATIONS, "key"));
184  shim()->ProcessSyncChanges(FROM_HERE, change_list);
185  scoped_ptr<Event> event = GetLastEvent();
186  ASSERT_TRUE(event);
187  EXPECT_EQ(synced_notifications_private::OnDataChanges::kEventName,
188            event->event_name);
189  ASSERT_TRUE(event->event_args);
190  EXPECT_EQ(1U, event->event_args->GetSize());
191
192  base::ListValue* args = NULL;
193  ASSERT_TRUE(event->event_args->GetList(0, &args));
194  EXPECT_EQ(1U, args->GetSize());
195  base::DictionaryValue* sync_change_value = NULL;
196  ASSERT_TRUE(args->GetDictionary(0, &sync_change_value));
197  scoped_ptr<synced_notifications_private::SyncChange> sync_change =
198      synced_notifications_private::SyncChange::FromValue(*sync_change_value);
199  ASSERT_TRUE(sync_change);
200  EXPECT_TRUE(ChangeSpecificsMatch(change_list[0],
201                                   sync_change->data.data_item));
202  EXPECT_EQ(synced_notifications_private::CHANGE_TYPE_UPDATED,
203            sync_change->change_type);
204}
205
206// Verify that multiple notification updates can be sent in one event.
207TEST_F(SyncedNotificationsShimTest, ProcessSyncChangesMultipleNotification) {
208  StartSync();
209  syncer::SyncChangeList change_list;
210  change_list.push_back(BuildChange(syncer::SYNCED_NOTIFICATIONS, "key"));
211  change_list.push_back(BuildChange(syncer::SYNCED_NOTIFICATIONS, "key2"));
212  shim()->ProcessSyncChanges(FROM_HERE, change_list);
213  scoped_ptr<Event> event = GetLastEvent();
214  ASSERT_TRUE(event);
215  EXPECT_EQ(synced_notifications_private::OnDataChanges::kEventName,
216            event->event_name);
217  ASSERT_TRUE(event->event_args);
218  base::ListValue* args = NULL;
219  ASSERT_TRUE(event->event_args->GetList(0, &args));
220  EXPECT_EQ(2U, args->GetSize());
221
222  base::DictionaryValue* sync_change_value = NULL;
223  ASSERT_TRUE(args->GetDictionary(0, &sync_change_value));
224  scoped_ptr<synced_notifications_private::SyncChange> sync_change =
225      synced_notifications_private::SyncChange::FromValue(*sync_change_value);
226  ASSERT_TRUE(sync_change);
227  EXPECT_TRUE(ChangeSpecificsMatch(change_list[0],
228                                   sync_change->data.data_item));
229  EXPECT_EQ(synced_notifications_private::CHANGE_TYPE_UPDATED,
230            sync_change->change_type);
231
232  ASSERT_TRUE(args->GetDictionary(1, &sync_change_value));
233  sync_change =
234      synced_notifications_private::SyncChange::FromValue(*sync_change_value);
235  ASSERT_TRUE(sync_change);
236  EXPECT_TRUE(ChangeSpecificsMatch(change_list[1],
237                                   sync_change->data.data_item));
238  EXPECT_EQ(synced_notifications_private::CHANGE_TYPE_UPDATED,
239            sync_change->change_type);
240}
241
242// Verify AppInfo updates trigger OnDataChanges events.
243TEST_F(SyncedNotificationsShimTest, ProcessSyncChangeAppInfo) {
244  StartSync();
245  syncer::SyncChangeList change_list;
246  change_list.push_back(
247      BuildChange(syncer::SYNCED_NOTIFICATION_APP_INFO, "key"));
248  shim()->ProcessSyncChanges(FROM_HERE, change_list);
249  scoped_ptr<Event> event = GetLastEvent();
250  ASSERT_TRUE(event);
251  EXPECT_EQ(synced_notifications_private::OnDataChanges::kEventName,
252            event->event_name);
253  ASSERT_TRUE(event->event_args);
254  EXPECT_EQ(1U, event->event_args->GetSize());
255
256  base::ListValue* args = NULL;
257  ASSERT_TRUE(event->event_args->GetList(0, &args));
258  EXPECT_EQ(1U, args->GetSize());
259  base::DictionaryValue* sync_change_value = NULL;
260  ASSERT_TRUE(args->GetDictionary(0, &sync_change_value));
261  scoped_ptr<synced_notifications_private::SyncChange> sync_change =
262      synced_notifications_private::SyncChange::FromValue(*sync_change_value);
263  ASSERT_TRUE(sync_change);
264  EXPECT_TRUE(ChangeSpecificsMatch(change_list[0],
265                                   sync_change->data.data_item));
266  EXPECT_EQ(synced_notifications_private::CHANGE_TYPE_UPDATED,
267            sync_change->change_type);
268}
269
270// Attempt to get the initial sync data both before and after sync starts.
271TEST_F(SyncedNotificationsShimTest, GetInitialData) {
272  std::vector<linked_ptr<synced_notifications_private::SyncData> > data;
273  EXPECT_FALSE(shim()->GetInitialData(
274      synced_notifications_private::SYNC_DATA_TYPE_SYNCED_NOTIFICATION, &data));
275  EXPECT_FALSE(shim()->GetInitialData(
276      synced_notifications_private::SYNC_DATA_TYPE_APP_INFO, &data));
277
278  StartSync();
279
280  EXPECT_TRUE(shim()->GetInitialData(
281      synced_notifications_private::SYNC_DATA_TYPE_SYNCED_NOTIFICATION, &data));
282  EXPECT_TRUE(data.empty());
283  EXPECT_TRUE(shim()->GetInitialData(
284      synced_notifications_private::SYNC_DATA_TYPE_APP_INFO, &data));
285  EXPECT_TRUE(data.empty());
286
287  notification_processor()->data().push_back(BuildData(
288      syncer::SYNCED_NOTIFICATIONS, "notif_key"));
289  EXPECT_TRUE(shim()->GetInitialData(
290      synced_notifications_private::SYNC_DATA_TYPE_SYNCED_NOTIFICATION, &data));
291  EXPECT_EQ(1U, data.size());
292  EXPECT_TRUE(DataSpecificsMatch(notification_processor()->data()[0],
293                                 data[0]->data_item));
294
295  data.clear();
296  app_info_processor()->data().push_back(BuildData(
297      syncer::SYNCED_NOTIFICATION_APP_INFO, "app_key"));
298  EXPECT_TRUE(shim()->GetInitialData(
299      synced_notifications_private::SYNC_DATA_TYPE_APP_INFO, &data));
300  EXPECT_EQ(1U, data.size());
301  EXPECT_TRUE(DataSpecificsMatch(app_info_processor()->data()[0],
302                                 data[0]->data_item));
303}
304
305// Verify that notification updates are properly handled.
306TEST_F(SyncedNotificationsShimTest, UpdateNotification) {
307  syncer::SyncData data =
308      BuildData(syncer::SYNCED_NOTIFICATIONS, "notif_key");
309  std::string serialized =
310      data.GetSpecifics().synced_notification().SerializeAsString();
311  EXPECT_FALSE(shim()->UpdateNotification(serialized));
312
313  StartSync();
314
315  EXPECT_FALSE(shim()->UpdateNotification("gibberish"));
316  EXPECT_TRUE(notification_processor()->changes().empty());
317
318  EXPECT_TRUE(shim()->UpdateNotification(serialized));
319  EXPECT_EQ(1U, notification_processor()->changes().size());
320  EXPECT_EQ(syncer::SyncChange::ACTION_UPDATE,
321            notification_processor()->changes()[0].change_type());
322  EXPECT_EQ(syncer::SYNCED_NOTIFICATIONS,
323            notification_processor()->changes()[0].sync_data().GetDataType());
324  EXPECT_EQ(serialized,
325            notification_processor()
326                ->changes()[0]
327                .sync_data()
328                .GetSpecifics()
329                .synced_notification()
330                .SerializeAsString());
331}
332
333// Verify that SetRenderContext updates the datatype context properly.
334TEST_F(SyncedNotificationsShimTest, SetRenderContext) {
335  const std::string kContext = "context";
336  EXPECT_FALSE(shim()->SetRenderContext(
337      synced_notifications_private::REFRESH_REQUEST_REFRESH_NEEDED, kContext));
338  EXPECT_FALSE(refresh_requested());
339
340  StartSync();
341
342  EXPECT_TRUE(shim()->SetRenderContext(
343      synced_notifications_private::REFRESH_REQUEST_REFRESH_NEEDED, kContext));
344  EXPECT_EQ(kContext, notification_processor()->context());
345  EXPECT_TRUE(refresh_requested());
346}
347
348}  // namespace
349