Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync / sessions2 / sessions_sync_manager_unittest.cc
1 // Copyright 2013 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/sync/sessions2/sessions_sync_manager.h"
6
7 #include "base/strings/string_util.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/sessions/session_id.h"
10 #include "chrome/browser/sessions/session_tab_helper.h"
11 #include "chrome/browser/sessions/session_types.h"
12 #include "chrome/browser/sync/glue/device_info.h"
13 #include "chrome/browser/sync/glue/session_sync_test_helper.h"
14 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
15 #include "chrome/browser/sync/glue/synced_window_delegate.h"
16 #include "chrome/browser/sync/sessions2/notification_service_sessions_router.h"
17 #include "chrome/browser/sync/sessions2/sessions_util.h"
18 #include "chrome/browser/sync/sessions2/synced_window_delegates_getter.h"
19 #include "chrome/browser/ui/sync/tab_contents_synced_tab_delegate.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/test/base/browser_with_test_window_test.h"
22 #include "components/sessions/serialized_navigation_entry_test_helper.h"
23 #include "content/public/browser/navigation_entry.h"
24 #include "content/public/browser/notification_details.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/notification_source.h"
27 #include "content/public/browser/web_contents.h"
28 #include "sync/api/sync_error_factory_mock.h"
29 #include "testing/gmock/include/gmock/gmock.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31
32 using content::WebContents;
33 using sessions::SerializedNavigationEntry;
34 using sessions::SerializedNavigationEntryTestHelper;
35 using syncer::SyncChange;
36 using syncer::SyncData;
37
38 namespace browser_sync {
39
40 namespace {
41
42 class SyncedWindowDelegateOverride : public SyncedWindowDelegate {
43  public:
44   explicit SyncedWindowDelegateOverride(SyncedWindowDelegate* wrapped)
45       : wrapped_(wrapped) {
46   }
47   virtual ~SyncedWindowDelegateOverride() {}
48
49   virtual bool HasWindow() const OVERRIDE {
50     return wrapped_->HasWindow();
51   }
52
53   virtual SessionID::id_type GetSessionId() const OVERRIDE {
54     return wrapped_->GetSessionId();
55   }
56
57   virtual int GetTabCount() const OVERRIDE {
58     return wrapped_->GetTabCount();
59   }
60
61   virtual int GetActiveIndex() const OVERRIDE {
62     return wrapped_->GetActiveIndex();
63   }
64
65   virtual bool IsApp() const OVERRIDE {
66     return wrapped_->IsApp();
67   }
68
69   virtual bool IsTypeTabbed() const OVERRIDE {
70     return wrapped_->IsTypeTabbed();
71   }
72
73   virtual bool IsTypePopup() const OVERRIDE {
74     return wrapped_->IsTypePopup();
75   }
76
77   virtual bool IsTabPinned(const SyncedTabDelegate* tab) const OVERRIDE {
78     return wrapped_->IsTabPinned(tab);
79   }
80
81   virtual SyncedTabDelegate* GetTabAt(int index) const OVERRIDE {
82     if (tab_overrides_.find(index) != tab_overrides_.end())
83       return tab_overrides_.find(index)->second;
84
85     return wrapped_->GetTabAt(index);
86   }
87
88   void OverrideTabAt(int index,
89                      SyncedTabDelegate* delegate,
90                      SessionID::id_type tab_id) {
91     tab_overrides_[index] = delegate;
92     tab_id_overrides_[index] = tab_id;
93   }
94
95   virtual SessionID::id_type GetTabIdAt(int index) const OVERRIDE {
96     if (tab_id_overrides_.find(index) != tab_id_overrides_.end())
97       return tab_id_overrides_.find(index)->second;
98     return wrapped_->GetTabIdAt(index);
99   }
100
101   virtual bool IsSessionRestoreInProgress() const OVERRIDE {
102     return wrapped_->IsSessionRestoreInProgress();
103   }
104
105  private:
106   std::map<int, SyncedTabDelegate*> tab_overrides_;
107   std::map<int, SessionID::id_type> tab_id_overrides_;
108   SyncedWindowDelegate* wrapped_;
109 };
110
111 class TestSyncedWindowDelegatesGetter : public SyncedWindowDelegatesGetter {
112  public:
113   TestSyncedWindowDelegatesGetter(
114       const std::set<SyncedWindowDelegate*>& delegates)
115       : delegates_(delegates) {}
116
117   virtual const std::set<SyncedWindowDelegate*> GetSyncedWindowDelegates()
118       OVERRIDE {
119     return delegates_;
120   }
121  private:
122   const std::set<SyncedWindowDelegate*> delegates_;
123 };
124
125 class TestSyncProcessorStub : public syncer::SyncChangeProcessor {
126  public:
127   explicit TestSyncProcessorStub(syncer::SyncChangeList* output)
128       : output_(output) {}
129   virtual syncer::SyncError ProcessSyncChanges(
130       const tracked_objects::Location& from_here,
131       const syncer::SyncChangeList& change_list) OVERRIDE {
132     if (error_.IsSet()) {
133       syncer::SyncError error = error_;
134       error_ = syncer::SyncError();
135       return error;
136     }
137
138     if (output_)
139       output_->insert(output_->end(), change_list.begin(), change_list.end());
140
141     return syncer::SyncError();
142   }
143
144   virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type)
145       const OVERRIDE {
146     return sync_data_to_return_;
147   }
148
149   void FailProcessSyncChangesWith(const syncer::SyncError& error) {
150     error_ = error;
151   }
152
153   void SetSyncDataToReturn(const syncer::SyncDataList& data) {
154     sync_data_to_return_ = data;
155   }
156
157  private:
158   syncer::SyncError error_;
159   syncer::SyncChangeList* output_;
160   syncer::SyncDataList sync_data_to_return_;
161 };
162
163 syncer::SyncChange MakeRemoteChange(
164     int64 id,
165     const sync_pb::SessionSpecifics& specifics,
166     SyncChange::SyncChangeType type) {
167   sync_pb::EntitySpecifics entity;
168   entity.mutable_session()->CopyFrom(specifics);
169   return syncer::SyncChange(
170       FROM_HERE, type,
171       syncer::SyncData::CreateRemoteData(id, entity, base::Time()));
172 }
173
174 void AddTabsToChangeList(
175       const std::vector<sync_pb::SessionSpecifics>& batch,
176       SyncChange::SyncChangeType type,
177       syncer::SyncChangeList* change_list) {
178   std::vector<sync_pb::SessionSpecifics>::const_iterator iter;
179   for (iter = batch.begin();
180        iter != batch.end(); ++iter) {
181     sync_pb::EntitySpecifics entity;
182     entity.mutable_session()->CopyFrom(*iter);
183     change_list->push_back(syncer::SyncChange(
184         FROM_HERE, type,
185         syncer::SyncData::CreateRemoteData(iter->tab_node_id(),
186                                            entity, base::Time())));
187   }
188 }
189
190 void AddTabsToSyncDataList(const std::vector<sync_pb::SessionSpecifics> tabs,
191                            syncer::SyncDataList* list) {
192   for (size_t i = 0; i < tabs.size(); i++) {
193     sync_pb::EntitySpecifics entity;
194     entity.mutable_session()->CopyFrom(tabs[i]);
195     list->push_back(SyncData::CreateRemoteData(
196         i + 2, entity, base::Time()));
197   }
198 }
199
200 class DummyRouter : public LocalSessionEventRouter {
201  public:
202   virtual ~DummyRouter() {}
203   virtual void StartRoutingTo(LocalSessionEventHandler* handler) OVERRIDE {}
204   virtual void Stop() OVERRIDE {}
205 };
206
207 scoped_ptr<LocalSessionEventRouter> NewDummyRouter() {
208   return scoped_ptr<LocalSessionEventRouter>(new DummyRouter());
209 }
210
211 }  // namespace
212
213 class SessionsSyncManagerTest
214     : public BrowserWithTestWindowTest,
215       public SessionsSyncManager::SyncInternalApiDelegate {
216  public:
217   SessionsSyncManagerTest() : test_processor_(NULL) {}
218
219   virtual void SetUp() OVERRIDE {
220     BrowserWithTestWindowTest::SetUp();
221     browser_sync::NotificationServiceSessionsRouter* router(
222         new browser_sync::NotificationServiceSessionsRouter(
223             profile(), syncer::SyncableService::StartSyncFlare()));
224     manager_.reset(new SessionsSyncManager(profile(), this,
225       scoped_ptr<LocalSessionEventRouter>(router)));
226   }
227
228   virtual void TearDown() OVERRIDE {
229     test_processor_ = NULL;
230     helper()->Reset();
231     manager_.reset();
232     BrowserWithTestWindowTest::TearDown();
233   }
234
235   virtual scoped_ptr<DeviceInfo> GetLocalDeviceInfo() const OVERRIDE {
236     return scoped_ptr<DeviceInfo>(
237         new DeviceInfo(GetLocalSyncCacheGUID(),
238                        "Wayne Gretzky's Hacking Box",
239                        "Chromium 10k",
240                        "Chrome 10k",
241                        sync_pb::SyncEnums_DeviceType_TYPE_LINUX));
242   }
243
244   virtual std::string GetLocalSyncCacheGUID() const OVERRIDE {
245     return "cache_guid";
246   }
247
248   SessionsSyncManager* manager() { return manager_.get(); }
249   SessionSyncTestHelper* helper() { return &helper_; }
250
251   void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data,
252                                   syncer::SyncChangeList* output) {
253     test_processor_ = new TestSyncProcessorStub(output);
254     syncer::SyncMergeResult result = manager_->MergeDataAndStartSyncing(
255         syncer::SESSIONS, initial_data,
256         scoped_ptr<syncer::SyncChangeProcessor>(test_processor_),
257         scoped_ptr<syncer::SyncErrorFactory>(
258             new syncer::SyncErrorFactoryMock()));
259     EXPECT_FALSE(result.error().IsSet());
260   }
261
262   void InitWithNoSyncData() {
263     InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL);
264   }
265
266   void TriggerProcessSyncChangesError() {
267     test_processor_->FailProcessSyncChangesWith(syncer::SyncError(
268         FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error",
269         syncer::SESSIONS));
270   }
271
272   void SetSyncData(const syncer::SyncDataList& data) {
273      test_processor_->SetSyncDataToReturn(data);
274   }
275
276   syncer::SyncChangeList* FilterOutLocalHeaderChanges(
277       syncer::SyncChangeList* list) {
278     syncer::SyncChangeList::iterator it = list->begin();
279     bool found = false;
280     while (it != list->end()) {
281       if (it->sync_data().GetTag() == manager_->current_machine_tag()) {
282         EXPECT_TRUE(SyncChange::ACTION_ADD == it->change_type() ||
283                     SyncChange::ACTION_UPDATE == it->change_type());
284         it = list->erase(it);
285         found = true;
286       } else {
287         ++it;
288       }
289     }
290     EXPECT_TRUE(found);
291     return list;
292   }
293
294  private:
295   scoped_ptr<SessionsSyncManager> manager_;
296   SessionSyncTestHelper helper_;
297   TestSyncProcessorStub* test_processor_;
298 };
299
300 // Test that the SyncSessionManager can properly fill in a SessionHeader.
301 TEST_F(SessionsSyncManagerTest, PopulateSessionHeader) {
302   sync_pb::SessionHeader header_s;
303   header_s.set_client_name("Client 1");
304   header_s.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN);
305
306   SyncedSession session;
307   base::Time time = base::Time::Now();
308   SessionsSyncManager::PopulateSessionHeaderFromSpecifics(
309       header_s, time, &session);
310   ASSERT_EQ("Client 1", session.session_name);
311   ASSERT_EQ(SyncedSession::TYPE_WIN, session.device_type);
312   ASSERT_EQ(time, session.modified_time);
313 }
314
315 // Test translation between protobuf types and chrome session types.
316 TEST_F(SessionsSyncManagerTest, PopulateSessionWindow) {
317   sync_pb::SessionWindow window_s;
318   window_s.add_tab(0);
319   window_s.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
320   window_s.set_selected_tab_index(1);
321
322   std::string tag = "tag";
323   SyncedSession* session = manager()->session_tracker_.GetSession(tag);
324   manager()->session_tracker_.PutWindowInSession(tag, 0);
325   manager()->BuildSyncedSessionFromSpecifics(
326       tag, window_s, base::Time(), session->windows[0]);
327   ASSERT_EQ(1U, session->windows[0]->tabs.size());
328   ASSERT_EQ(1, session->windows[0]->selected_tab_index);
329   ASSERT_EQ(1, session->windows[0]->type);
330   ASSERT_EQ(1U, manager()->session_tracker_.num_synced_sessions());
331   ASSERT_EQ(1U,
332             manager()->session_tracker_.num_synced_tabs(std::string("tag")));
333 }
334
335 namespace {
336
337 class SyncedTabDelegateFake : public SyncedTabDelegate {
338  public:
339   SyncedTabDelegateFake() : current_entry_index_(0),
340                             pending_entry_index_(-1),
341                             is_managed_(false),
342                             sync_id_(-1),
343                             blocked_navigations_(NULL) {}
344   virtual ~SyncedTabDelegateFake() {}
345
346   virtual int GetCurrentEntryIndex() const OVERRIDE {
347     return current_entry_index_;
348   }
349   void set_current_entry_index(int i) {
350     current_entry_index_ = i;
351   }
352
353   virtual content::NavigationEntry* GetEntryAtIndex(int i) const OVERRIDE {
354     const int size = entries_.size();
355     return (size < i + 1) ? NULL : entries_[i];
356   }
357
358   void AppendEntry(content::NavigationEntry* entry) {
359     entries_.push_back(entry);
360   }
361
362   virtual int GetEntryCount() const OVERRIDE {
363     return entries_.size();
364   }
365
366   virtual int GetPendingEntryIndex() const OVERRIDE {
367     return pending_entry_index_;
368   }
369   void set_pending_entry_index(int i) {
370     pending_entry_index_ = i;
371   }
372
373   virtual SessionID::id_type GetWindowId() const OVERRIDE {
374     return SessionID::id_type();
375   }
376
377   virtual SessionID::id_type GetSessionId() const OVERRIDE {
378     return SessionID::id_type();
379   }
380
381   virtual bool IsBeingDestroyed() const OVERRIDE { return false; }
382   virtual Profile* profile() const OVERRIDE { return NULL; }
383   virtual std::string GetExtensionAppId() const OVERRIDE {
384     return std::string();
385   }
386   virtual content::NavigationEntry* GetPendingEntry() const OVERRIDE {
387    return NULL;
388   }
389   virtual content::NavigationEntry* GetActiveEntry() const OVERRIDE {
390    return NULL;
391   }
392   virtual bool ProfileIsManaged() const OVERRIDE {
393    return is_managed_;
394   }
395   void set_is_managed(bool is_managed) { is_managed_ = is_managed; }
396   virtual const std::vector<const content::NavigationEntry*>*
397       GetBlockedNavigations() const OVERRIDE {
398     return blocked_navigations_;
399   }
400   void set_blocked_navigations(
401       std::vector<const content::NavigationEntry*>* navs) {
402     blocked_navigations_ = navs;
403   }
404   virtual bool IsPinned() const OVERRIDE {
405    return false;
406   }
407   virtual bool HasWebContents() const OVERRIDE {
408    return false;
409   }
410   virtual content::WebContents* GetWebContents() const OVERRIDE {
411    return NULL;
412   }
413
414   // Session sync related methods.
415   virtual int GetSyncId() const OVERRIDE {
416    return sync_id_;
417   }
418   virtual void SetSyncId(int sync_id) OVERRIDE {
419     sync_id_ = sync_id;
420   }
421
422   void reset() {
423     current_entry_index_ = 0;
424     pending_entry_index_ = -1;
425     sync_id_ = -1;
426     entries_.clear();
427   }
428
429  private:
430    int current_entry_index_;
431    int pending_entry_index_;
432    bool is_managed_;
433    int sync_id_;
434    std::vector<const content::NavigationEntry*>* blocked_navigations_;
435    ScopedVector<content::NavigationEntry> entries_;
436 };
437
438 }  // namespace
439
440 // Test that we exclude tabs with only chrome:// and file:// schemed navigations
441 // from ShouldSyncTab(..).
442 TEST_F(SessionsSyncManagerTest, ValidTabs) {
443   SyncedTabDelegateFake tab;
444
445   // A null entry shouldn't crash.
446   tab.AppendEntry(NULL);
447   EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
448   tab.reset();
449
450   // A chrome:// entry isn't valid.
451   content::NavigationEntry* entry(content::NavigationEntry::Create());
452   entry->SetVirtualURL(GURL("chrome://preferences/"));
453   tab.AppendEntry(entry);
454   EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
455
456
457   // A file:// entry isn't valid, even in addition to another entry.
458   content::NavigationEntry* entry2(content::NavigationEntry::Create());
459   entry2->SetVirtualURL(GURL("file://bla"));
460   tab.AppendEntry(entry2);
461   EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
462
463   // Add a valid scheme entry to tab, making the tab valid.
464   content::NavigationEntry* entry3(content::NavigationEntry::Create());
465   entry3->SetVirtualURL(GURL("http://www.google.com"));
466   tab.AppendEntry(entry3);
467   EXPECT_FALSE(sessions_util::ShouldSyncTab(tab));
468 }
469
470 // Make sure GetCurrentVirtualURL() returns the virtual URL of the pending
471 // entry if the current entry is pending.
472 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLPending) {
473   SyncedTabDelegateFake tab;
474   content::NavigationEntry* entry(content::NavigationEntry::Create());
475   entry->SetVirtualURL(GURL("http://www.google.com"));
476   tab.AppendEntry(entry);
477   EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
478 }
479
480 // Make sure GetCurrentVirtualURL() returns the virtual URL of the current
481 // entry if the current entry is non-pending.
482 TEST_F(SessionsSyncManagerTest, GetCurrentVirtualURLNonPending) {
483   SyncedTabDelegateFake tab;
484   content::NavigationEntry* entry(content::NavigationEntry::Create());
485   entry->SetVirtualURL(GURL("http://www.google.com"));
486   tab.AppendEntry(entry);
487   EXPECT_EQ(entry->GetVirtualURL(), manager()->GetCurrentVirtualURL(tab));
488 }
489
490 static const base::Time kTime1 = base::Time::FromInternalValue(100);
491 static const base::Time kTime2 = base::Time::FromInternalValue(105);
492 static const base::Time kTime3 = base::Time::FromInternalValue(110);
493 static const base::Time kTime4 = base::Time::FromInternalValue(120);
494 static const base::Time kTime5 = base::Time::FromInternalValue(130);
495
496 // Populate the mock tab delegate with some data and navigation
497 // entries and make sure that setting a SessionTab from it preserves
498 // those entries (and clobbers any existing data).
499 TEST_F(SessionsSyncManagerTest, SetSessionTabFromDelegate) {
500   // Create a tab with three valid entries.
501   SyncedTabDelegateFake tab;
502   content::NavigationEntry* entry1(content::NavigationEntry::Create());
503   entry1->SetVirtualURL(GURL("http://www.google.com"));
504   entry1->SetTimestamp(kTime1);
505   entry1->SetHttpStatusCode(200);
506   content::NavigationEntry* entry2(content::NavigationEntry::Create());
507   entry2->SetVirtualURL(GURL("http://www.noodle.com"));
508   entry2->SetTimestamp(kTime2);
509   entry2->SetHttpStatusCode(201);
510   content::NavigationEntry* entry3(content::NavigationEntry::Create());
511   entry3->SetVirtualURL(GURL("http://www.doodle.com"));
512   entry3->SetTimestamp(kTime3);
513   entry3->SetHttpStatusCode(202);
514
515   tab.AppendEntry(entry1);
516   tab.AppendEntry(entry2);
517   tab.AppendEntry(entry3);
518   tab.set_current_entry_index(2);
519
520   SessionTab session_tab;
521   session_tab.window_id.set_id(1);
522   session_tab.tab_id.set_id(1);
523   session_tab.tab_visual_index = 1;
524   session_tab.current_navigation_index = 1;
525   session_tab.pinned = true;
526   session_tab.extension_app_id = "app id";
527   session_tab.user_agent_override = "override";
528   session_tab.timestamp = kTime5;
529   session_tab.navigations.push_back(
530       SerializedNavigationEntryTestHelper::CreateNavigation(
531           "http://www.example.com", "Example"));
532   session_tab.session_storage_persistent_id = "persistent id";
533   manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
534
535   EXPECT_EQ(0, session_tab.window_id.id());
536   EXPECT_EQ(0, session_tab.tab_id.id());
537   EXPECT_EQ(0, session_tab.tab_visual_index);
538   EXPECT_EQ(2, session_tab.current_navigation_index);
539   EXPECT_FALSE(session_tab.pinned);
540   EXPECT_TRUE(session_tab.extension_app_id.empty());
541   EXPECT_TRUE(session_tab.user_agent_override.empty());
542   EXPECT_EQ(kTime4, session_tab.timestamp);
543   ASSERT_EQ(3u, session_tab.navigations.size());
544   EXPECT_EQ(entry1->GetVirtualURL(),
545             session_tab.navigations[0].virtual_url());
546   EXPECT_EQ(entry2->GetVirtualURL(),
547             session_tab.navigations[1].virtual_url());
548   EXPECT_EQ(entry3->GetVirtualURL(),
549             session_tab.navigations[2].virtual_url());
550   EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
551   EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
552   EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
553   EXPECT_EQ(200, session_tab.navigations[0].http_status_code());
554   EXPECT_EQ(201, session_tab.navigations[1].http_status_code());
555   EXPECT_EQ(202, session_tab.navigations[2].http_status_code());
556   EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
557             session_tab.navigations[0].blocked_state());
558   EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
559             session_tab.navigations[1].blocked_state());
560   EXPECT_EQ(SerializedNavigationEntry::STATE_INVALID,
561             session_tab.navigations[2].blocked_state());
562   EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
563 }
564
565 // Tests that for managed users blocked navigations are recorded and marked as
566 // such, while regular navigations are marked as allowed.
567 TEST_F(SessionsSyncManagerTest, BlockedNavigations) {
568   SyncedTabDelegateFake tab;
569   content::NavigationEntry* entry1(content::NavigationEntry::Create());
570   entry1->SetVirtualURL(GURL("http://www.google.com"));
571   entry1->SetTimestamp(kTime1);
572   tab.AppendEntry(entry1);
573
574   content::NavigationEntry* entry2 = content::NavigationEntry::Create();
575   entry2->SetVirtualURL(GURL("http://blocked.com/foo"));
576   entry2->SetTimestamp(kTime2);
577   content::NavigationEntry* entry3 = content::NavigationEntry::Create();
578   entry3->SetVirtualURL(GURL("http://evil.com"));
579   entry3->SetTimestamp(kTime3);
580   ScopedVector<const content::NavigationEntry> blocked_navigations;
581   blocked_navigations.push_back(entry2);
582   blocked_navigations.push_back(entry3);
583
584   tab.set_is_managed(true);
585   tab.set_blocked_navigations(&blocked_navigations.get());
586
587   SessionTab session_tab;
588   session_tab.window_id.set_id(1);
589   session_tab.tab_id.set_id(1);
590   session_tab.tab_visual_index = 1;
591   session_tab.current_navigation_index = 1;
592   session_tab.pinned = true;
593   session_tab.extension_app_id = "app id";
594   session_tab.user_agent_override = "override";
595   session_tab.timestamp = kTime5;
596   session_tab.navigations.push_back(
597       SerializedNavigationEntryTestHelper::CreateNavigation(
598           "http://www.example.com", "Example"));
599   session_tab.session_storage_persistent_id = "persistent id";
600   manager()->SetSessionTabFromDelegate(tab, kTime4, &session_tab);
601
602   EXPECT_EQ(0, session_tab.window_id.id());
603   EXPECT_EQ(0, session_tab.tab_id.id());
604   EXPECT_EQ(0, session_tab.tab_visual_index);
605   EXPECT_EQ(0, session_tab.current_navigation_index);
606   EXPECT_FALSE(session_tab.pinned);
607   EXPECT_TRUE(session_tab.extension_app_id.empty());
608   EXPECT_TRUE(session_tab.user_agent_override.empty());
609   EXPECT_EQ(kTime4, session_tab.timestamp);
610   ASSERT_EQ(3u, session_tab.navigations.size());
611   EXPECT_EQ(entry1->GetVirtualURL(),
612             session_tab.navigations[0].virtual_url());
613   EXPECT_EQ(entry2->GetVirtualURL(),
614             session_tab.navigations[1].virtual_url());
615   EXPECT_EQ(entry3->GetVirtualURL(),
616             session_tab.navigations[2].virtual_url());
617   EXPECT_EQ(kTime1, session_tab.navigations[0].timestamp());
618   EXPECT_EQ(kTime2, session_tab.navigations[1].timestamp());
619   EXPECT_EQ(kTime3, session_tab.navigations[2].timestamp());
620   EXPECT_EQ(SerializedNavigationEntry::STATE_ALLOWED,
621             session_tab.navigations[0].blocked_state());
622   EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
623             session_tab.navigations[1].blocked_state());
624   EXPECT_EQ(SerializedNavigationEntry::STATE_BLOCKED,
625             session_tab.navigations[2].blocked_state());
626   EXPECT_TRUE(session_tab.session_storage_persistent_id.empty());
627 }
628
629 // Tests that the local session header objects is created properly in
630 // presence of no other session activity, once and only once.
631 TEST_F(SessionsSyncManagerTest, MergeLocalSessionNoTabs) {
632   syncer::SyncChangeList out;
633   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
634   EXPECT_FALSE(manager()->current_machine_tag().empty());
635
636   EXPECT_EQ(2U, out.size());
637   EXPECT_TRUE(out[0].IsValid());
638   EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
639   const SyncData data(out[0].sync_data());
640   EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
641   const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
642   EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
643   EXPECT_TRUE(specifics.has_header());
644   const sync_pb::SessionHeader& header_s = specifics.header();
645   EXPECT_TRUE(header_s.has_device_type());
646   EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
647   EXPECT_EQ(0, header_s.window_size());
648
649   EXPECT_TRUE(out[1].IsValid());
650   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
651   const SyncData data_2(out[1].sync_data());
652   EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
653   const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
654   EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
655   EXPECT_TRUE(specifics2.has_header());
656   const sync_pb::SessionHeader& header_s2 = specifics2.header();
657   EXPECT_EQ(0, header_s2.window_size());
658
659   // Now take that header node and feed it in as input.
660   SyncData d(SyncData::CreateRemoteData(1, data.GetSpecifics(), base::Time()));
661   syncer::SyncDataList in(&d, &d + 1);
662   out.clear();
663   SessionsSyncManager manager2(profile(), this, NewDummyRouter());
664   syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
665       syncer::SESSIONS, in,
666       scoped_ptr<syncer::SyncChangeProcessor>(
667           new TestSyncProcessorStub(&out)),
668       scoped_ptr<syncer::SyncErrorFactory>(
669           new syncer::SyncErrorFactoryMock()));
670   ASSERT_FALSE(result.error().IsSet());
671
672   EXPECT_EQ(1U, out.size());
673   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
674   EXPECT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
675 }
676
677 // Ensure model association associates the pre-existing tabs.
678 TEST_F(SessionsSyncManagerTest, SwappedOutOnRestore) {
679   AddTab(browser(), GURL("http://foo1"));
680   NavigateAndCommitActiveTab(GURL("http://foo2"));
681   AddTab(browser(), GURL("http://bar1"));
682   NavigateAndCommitActiveTab(GURL("http://bar2"));
683   AddTab(browser(), GURL("http://baz1"));
684   NavigateAndCommitActiveTab(GURL("http://baz2"));
685   const int kRestoredTabId = 1337;
686   const int kNewTabId = 2468;
687
688   syncer::SyncDataList in;
689   syncer::SyncChangeList out;
690   InitWithSyncDataTakeOutput(in, &out);
691
692   // Should be one header add, 3 tab add/update pairs, one header update.
693   ASSERT_EQ(8U, out.size());
694
695   // For input, we set up:
696   // * one "normal" fully loaded tab
697   // * one "frozen" tab with no WebContents and a tab_id change
698   // * one "frozen" tab with no WebContents and no tab_id change
699   SyncData t0(SyncData::CreateRemoteData(1, out[2].sync_data().GetSpecifics(),
700                                          base::Time()));
701   sync_pb::EntitySpecifics entity(out[4].sync_data().GetSpecifics());
702   entity.mutable_session()->mutable_tab()->set_tab_id(kRestoredTabId);
703   SyncData t1(SyncData::CreateRemoteData(2, entity, base::Time()));
704   SyncData t2(SyncData::CreateRemoteData(3, out[6].sync_data().GetSpecifics(),
705                                          base::Time()));
706   in.push_back(t0);
707   in.push_back(t1);
708   in.push_back(t2);
709   out.clear();
710   manager()->StopSyncing(syncer::SESSIONS);
711
712   const std::set<SyncedWindowDelegate*> windows(
713       SyncedWindowDelegate::GetSyncedWindowDelegates());
714   ASSERT_EQ(1U, windows.size());
715   SyncedTabDelegateFake t1_override, t2_override;
716   t1_override.SetSyncId(1);  // No WebContents by default.
717   t2_override.SetSyncId(2);  // No WebContents by default.
718   SyncedWindowDelegateOverride window_override(*windows.begin());
719   window_override.OverrideTabAt(1, &t1_override, kNewTabId);
720   window_override.OverrideTabAt(2, &t2_override,
721                                 t2.GetSpecifics().session().tab().tab_id());
722   std::set<SyncedWindowDelegate*> delegates;
723   delegates.insert(&window_override);
724   scoped_ptr<TestSyncedWindowDelegatesGetter> getter(
725       new TestSyncedWindowDelegatesGetter(delegates));
726   manager()->synced_window_getter_.reset(getter.release());
727
728   syncer::SyncMergeResult result = manager()->MergeDataAndStartSyncing(
729       syncer::SESSIONS, in,
730       scoped_ptr<syncer::SyncChangeProcessor>(
731           new TestSyncProcessorStub(&out)),
732       scoped_ptr<syncer::SyncErrorFactory>(
733           new syncer::SyncErrorFactoryMock()));
734
735   // There should be two changes, one for the fully associated tab, and
736   // one for the tab_id update to t1.  t2 shouldn't need to be updated.
737   ASSERT_EQ(2U, FilterOutLocalHeaderChanges(&out)->size());
738   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
739   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
740   EXPECT_EQ(kNewTabId,
741             out[1].sync_data().GetSpecifics().session().tab().tab_id());
742
743   // Verify TabLinks.
744   SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
745   ASSERT_EQ(3U, tab_map.size());
746   int t2_tab_id = t2.GetSpecifics().session().tab().tab_id();
747   EXPECT_EQ(2, tab_map.find(t2_tab_id)->second->tab_node_id());
748   EXPECT_EQ(1, tab_map.find(kNewTabId)->second->tab_node_id());
749   int t0_tab_id = out[0].sync_data().GetSpecifics().session().tab().tab_id();
750   EXPECT_EQ(0, tab_map.find(t0_tab_id)->second->tab_node_id());
751   // TODO(tim): Once bug 337057 is fixed, we can issue an OnLocalTabModified
752   // from here (using an override similar to above) to return a new tab id
753   // and verify that we don't see any node creations in the SyncChangeProcessor
754   // (similar to how SessionsSyncManagerTest.OnLocalTabModified works.)
755 }
756
757 // Tests MergeDataAndStartSyncing with sync data but no local data.
758 TEST_F(SessionsSyncManagerTest, MergeWithInitialForeignSession) {
759   std::string tag = "tag1";
760
761   SessionID::id_type n1[] = {5, 10, 13, 17};
762   std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
763   std::vector<sync_pb::SessionSpecifics> tabs1;
764   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
765       tag, tab_list1, &tabs1));
766   // Add a second window.
767   SessionID::id_type n2[] = {7, 15, 18, 20};
768   std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
769   helper()->AddWindowSpecifics(1, tab_list2, &meta);
770
771   // Set up initial data.
772   syncer::SyncDataList initial_data;
773   sync_pb::EntitySpecifics entity;
774   entity.mutable_session()->CopyFrom(meta);
775   initial_data.push_back(SyncData::CreateRemoteData(1, entity, base::Time()));
776   AddTabsToSyncDataList(tabs1, &initial_data);
777
778   for (size_t i = 0; i < tab_list2.size(); ++i) {
779     sync_pb::EntitySpecifics entity;
780     helper()->BuildTabSpecifics(tag, 0, tab_list2[i],
781                                 entity.mutable_session());
782     initial_data.push_back(
783         SyncData::CreateRemoteData(i + 10, entity, base::Time()));
784   }
785
786   syncer::SyncChangeList output;
787   InitWithSyncDataTakeOutput(initial_data, &output);
788   EXPECT_TRUE(FilterOutLocalHeaderChanges(&output)->empty());
789
790   std::vector<const SyncedSession*> foreign_sessions;
791   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
792   ASSERT_EQ(1U, foreign_sessions.size());
793   std::vector<std::vector<SessionID::id_type> > session_reference;
794   session_reference.push_back(tab_list1);
795   session_reference.push_back(tab_list2);
796   helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
797 }
798
799 // This is a combination of MergeWithInitialForeignSession and
800 // MergeLocalSessionExistingTabs. We repeat some checks performed in each of
801 // those tests to ensure the common mixed scenario works.
802 TEST_F(SessionsSyncManagerTest, MergeWithLocalAndForeignTabs) {
803   // Local.
804   AddTab(browser(), GURL("http://foo1"));
805   NavigateAndCommitActiveTab(GURL("http://foo2"));
806
807   // Foreign.
808   std::string tag = "tag1";
809   SessionID::id_type n1[] = {5, 10, 13, 17};
810   std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
811   std::vector<sync_pb::SessionSpecifics> tabs1;
812   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
813       tag, tab_list1, &tabs1));
814   syncer::SyncDataList foreign_data;
815   sync_pb::EntitySpecifics entity;
816   entity.mutable_session()->CopyFrom(meta);
817   foreign_data.push_back(SyncData::CreateRemoteData(1, entity, base::Time()));
818   AddTabsToSyncDataList(tabs1, &foreign_data);
819
820   syncer::SyncChangeList output;
821   InitWithSyncDataTakeOutput(foreign_data, &output);
822   ASSERT_EQ(4U, output.size());
823
824   // Verify the local header.
825   EXPECT_TRUE(output[0].IsValid());
826   EXPECT_EQ(SyncChange::ACTION_ADD, output[0].change_type());
827   const SyncData data(output[0].sync_data());
828   EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
829   const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
830   EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
831   EXPECT_TRUE(specifics.has_header());
832   const sync_pb::SessionHeader& header_s = specifics.header();
833   EXPECT_TRUE(header_s.has_device_type());
834   EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
835   EXPECT_EQ(0, header_s.window_size());
836
837   // Verify the tab node creations and updates with content.
838   for (int i = 1; i < 3; i++) {
839     EXPECT_TRUE(output[i].IsValid());
840     const SyncData data(output[i].sync_data());
841     EXPECT_TRUE(StartsWithASCII(data.GetTag(),
842                                 manager()->current_machine_tag(), true));
843     const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
844     EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
845   }
846   EXPECT_EQ(SyncChange::ACTION_ADD, output[1].change_type());
847   EXPECT_EQ(SyncChange::ACTION_UPDATE, output[2].change_type());
848   EXPECT_TRUE(output[2].sync_data().GetSpecifics().session().has_tab());
849
850   // Verify the header was updated to reflect window state.
851   EXPECT_TRUE(output[3].IsValid());
852   EXPECT_EQ(SyncChange::ACTION_UPDATE, output[3].change_type());
853   const SyncData data_2(output[3].sync_data());
854   EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
855   const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
856   EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
857   EXPECT_TRUE(specifics2.has_header());
858   const sync_pb::SessionHeader& header_s2 = specifics2.header();
859   EXPECT_EQ(1, header_s2.window_size());
860
861   // Verify foreign data.
862   std::vector<const SyncedSession*> foreign_sessions;
863   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
864   std::vector<std::vector<SessionID::id_type> > session_reference;
865   session_reference.push_back(tab_list1);
866   helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
867   // There should be one and only one foreign session. If VerifySyncedSession
868   // was successful above this EXPECT call ensures the local session didn't
869   // get mistakenly added to foreign tracking (Similar to ExistingTabs test).
870   EXPECT_EQ(1U, foreign_sessions.size());
871 }
872
873 // Tests the common scenario.  Merge with both local and foreign session data
874 // followed by updates flowing from sync and local.
875 TEST_F(SessionsSyncManagerTest, UpdatesAfterMixedMerge) {
876   // Add local and foreign data.
877   AddTab(browser(), GURL("http://foo1"));
878   NavigateAndCommitActiveTab(GURL("http://foo2"));
879
880   std::string tag1 = "tag1";
881   syncer::SyncDataList foreign_data1;
882   std::vector<std::vector<SessionID::id_type> > meta1_reference;
883   sync_pb::SessionSpecifics meta1;
884
885   SessionID::id_type n1[] = {5, 10, 13, 17};
886   std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
887   meta1_reference.push_back(tab_list1);
888   std::vector<sync_pb::SessionSpecifics> tabs1;
889   meta1 = helper()->BuildForeignSession(tag1, tab_list1, &tabs1);
890   sync_pb::EntitySpecifics entity;
891   entity.mutable_session()->CopyFrom(meta1);
892   foreign_data1.push_back(SyncData::CreateRemoteData(
893       1, entity, base::Time()));
894   AddTabsToSyncDataList(tabs1, &foreign_data1);
895
896   syncer::SyncChangeList output1;
897   InitWithSyncDataTakeOutput(foreign_data1, &output1);
898   ASSERT_EQ(4U, output1.size());
899
900   // Add a second window to the foreign session.
901   // TODO(tim): Bug 98892. Add local window too when observers are hooked up.
902   SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
903   std::vector<SessionID::id_type> tab_list2(
904       tab_nums2, tab_nums2 + arraysize(tab_nums2));
905   meta1_reference.push_back(tab_list2);
906   helper()->AddWindowSpecifics(1, tab_list2, &meta1);
907   std::vector<sync_pb::SessionSpecifics> tabs2;
908   tabs2.resize(tab_list2.size());
909   for (size_t i = 0; i < tab_list2.size(); ++i) {
910     helper()->BuildTabSpecifics(tag1, 0, tab_list2[i], &tabs2[i]);
911   }
912
913   syncer::SyncChangeList changes;
914   changes.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
915   AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
916   manager()->ProcessSyncChanges(FROM_HERE, changes);
917   changes.clear();
918
919   // Check that the foreign session was associated and retrieve the data.
920   std::vector<const SyncedSession*> foreign_sessions;
921   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
922   ASSERT_EQ(1U, foreign_sessions.size());
923   ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
924   ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
925   helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
926
927   // Add a new foreign session.
928   std::string tag2 = "tag2";
929   SessionID::id_type n2[] = {107, 115};
930   std::vector<SessionID::id_type> tag2_tab_list(n2, n2 + arraysize(n2));
931   std::vector<sync_pb::SessionSpecifics> tag2_tabs;
932   sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
933       tag2, tag2_tab_list, &tag2_tabs));
934   changes.push_back(MakeRemoteChange(100, meta2, SyncChange::ACTION_ADD));
935   AddTabsToChangeList(tag2_tabs, SyncChange::ACTION_ADD, &changes);
936
937   manager()->ProcessSyncChanges(FROM_HERE, changes);
938   changes.clear();
939
940   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
941   std::vector<std::vector<SessionID::id_type> > meta2_reference;
942   meta2_reference.push_back(tag2_tab_list);
943   ASSERT_EQ(2U, foreign_sessions.size());
944   ASSERT_EQ(2U, foreign_sessions[1]->windows.find(0)->second->tabs.size());
945   helper()->VerifySyncedSession(tag2, meta2_reference, *(foreign_sessions[1]));
946   foreign_sessions.clear();
947
948   // Remove a tab from a window.
949   meta1_reference[0].pop_back();
950   tab_list1.pop_back();
951   sync_pb::SessionWindow* win = meta1.mutable_header()->mutable_window(0);
952   win->clear_tab();
953   for (std::vector<int>::const_iterator iter = tab_list1.begin();
954        iter != tab_list1.end(); ++iter) {
955     win->add_tab(*iter);
956   }
957   syncer::SyncChangeList removal;
958   removal.push_back(MakeRemoteChange(1, meta1, SyncChange::ACTION_UPDATE));
959   AddTabsToChangeList(tabs1, SyncChange::ACTION_UPDATE, &removal);
960   manager()->ProcessSyncChanges(FROM_HERE, removal);
961
962   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
963   ASSERT_EQ(2U, foreign_sessions.size());
964   ASSERT_EQ(3U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
965   helper()->VerifySyncedSession(tag1, meta1_reference, *(foreign_sessions[0]));
966 }
967
968 // Tests that this SyncSessionManager knows how to delete foreign sessions
969 // if it wants to.
970 TEST_F(SessionsSyncManagerTest, DeleteForeignSession) {
971   InitWithNoSyncData();
972   std::string tag = "tag1";
973   syncer::SyncChangeList changes;
974
975   std::vector<const SyncedSession*> foreign_sessions;
976   ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
977   manager()->DeleteForeignSessionInternal(tag, &changes);
978   ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
979   EXPECT_TRUE(changes.empty());
980
981    // Fill an instance of session specifics with a foreign session's data.
982   std::vector<sync_pb::SessionSpecifics> tabs;
983   SessionID::id_type n1[] = {5, 10, 13, 17};
984   std::vector<SessionID::id_type> tab_nums1(n1, n1 + arraysize(n1));
985   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
986       tag, tab_nums1, &tabs));
987
988   // Update associator with the session's meta node, window, and tabs.
989   manager()->UpdateTrackerWithForeignSession(meta, base::Time());
990   for (std::vector<sync_pb::SessionSpecifics>::iterator iter = tabs.begin();
991        iter != tabs.end(); ++iter) {
992     manager()->UpdateTrackerWithForeignSession(*iter, base::Time());
993   }
994   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
995   ASSERT_EQ(1U, foreign_sessions.size());
996
997   // Now delete the foreign session.
998   manager()->DeleteForeignSessionInternal(tag, &changes);
999   EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1000
1001   EXPECT_EQ(5U, changes.size());
1002   std::set<std::string> expected_tags(&tag, &tag + 1);
1003   for (int i = 0; i < 5; i++)
1004     expected_tags.insert(TabNodePool2::TabIdToTag(tag, i));
1005
1006   for (int i = 0; i < 5; i++) {
1007     SCOPED_TRACE(changes[i].ToString());
1008     EXPECT_TRUE(changes[i].IsValid());
1009     EXPECT_EQ(SyncChange::ACTION_DELETE, changes[i].change_type());
1010     EXPECT_TRUE(changes[i].sync_data().IsValid());
1011     EXPECT_EQ(1U, expected_tags.erase(changes[i].sync_data().GetTag()));
1012   }
1013 }
1014
1015 // Write a foreign session to a node, with the tabs arriving first, and then
1016 // retrieve it.
1017 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeTabsFirst) {
1018   InitWithNoSyncData();
1019
1020   // Fill an instance of session specifics with a foreign session's data.
1021   std::string tag = "tag1";
1022   SessionID::id_type nums1[] = {5, 10, 13, 17};
1023   std::vector<sync_pb::SessionSpecifics> tabs1;
1024   std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1025   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1026       tag, tab_list1, &tabs1));
1027
1028   syncer::SyncChangeList adds;
1029   // Add tabs for first window, then the meta node.
1030   AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &adds);
1031   adds.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1032   manager()->ProcessSyncChanges(FROM_HERE, adds);
1033
1034   // Check that the foreign session was associated and retrieve the data.
1035   std::vector<const SyncedSession*> foreign_sessions;
1036   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1037   ASSERT_EQ(1U, foreign_sessions.size());
1038   std::vector<std::vector<SessionID::id_type> > session_reference;
1039   session_reference.push_back(tab_list1);
1040   helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1041 }
1042
1043 // Write a foreign session to a node with some tabs that never arrive.
1044 TEST_F(SessionsSyncManagerTest, WriteForeignSessionToNodeMissingTabs) {
1045   InitWithNoSyncData();
1046
1047   // Fill an instance of session specifics with a foreign session's data.
1048   std::string tag = "tag1";
1049   SessionID::id_type nums1[] = {5, 10, 13, 17};
1050   std::vector<sync_pb::SessionSpecifics> tabs1;
1051   std::vector<SessionID::id_type> tab_list1 (nums1, nums1 + arraysize(nums1));
1052   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1053       tag, tab_list1, &tabs1));
1054   // Add a second window, but this time only create two tab nodes, despite the
1055   // window expecting four tabs.
1056   SessionID::id_type tab_nums2[] = {7, 15, 18, 20};
1057   std::vector<SessionID::id_type> tab_list2(
1058       tab_nums2, tab_nums2 + arraysize(tab_nums2));
1059   helper()->AddWindowSpecifics(1, tab_list2, &meta);
1060   std::vector<sync_pb::SessionSpecifics> tabs2;
1061   tabs2.resize(2);
1062   for (size_t i = 0; i < 2; ++i) {
1063     helper()->BuildTabSpecifics(tag, 0, tab_list2[i], &tabs2[i]);
1064   }
1065
1066   syncer::SyncChangeList changes;
1067   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1068   AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1069   AddTabsToChangeList(tabs2, SyncChange::ACTION_ADD, &changes);
1070   manager()->ProcessSyncChanges(FROM_HERE, changes);
1071   changes.clear();
1072
1073   // Check that the foreign session was associated and retrieve the data.
1074   std::vector<const SyncedSession*> foreign_sessions;
1075   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1076   ASSERT_EQ(1U, foreign_sessions.size());
1077   ASSERT_EQ(2U, foreign_sessions[0]->windows.size());
1078   ASSERT_EQ(4U, foreign_sessions[0]->windows.find(0)->second->tabs.size());
1079   ASSERT_EQ(4U, foreign_sessions[0]->windows.find(1)->second->tabs.size());
1080
1081   // Close the second window.
1082   meta.mutable_header()->clear_window();
1083   helper()->AddWindowSpecifics(0, tab_list1, &meta);
1084   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_UPDATE));
1085   // Update associator with the session's meta node containing one window.
1086   manager()->ProcessSyncChanges(FROM_HERE, changes);
1087
1088   // Check that the foreign session was associated and retrieve the data.
1089   foreign_sessions.clear();
1090   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1091   ASSERT_EQ(1U, foreign_sessions.size());
1092   ASSERT_EQ(1U, foreign_sessions[0]->windows.size());
1093   std::vector<std::vector<SessionID::id_type> > session_reference;
1094   session_reference.push_back(tab_list1);
1095   helper()->VerifySyncedSession(tag, session_reference, *(foreign_sessions[0]));
1096 }
1097
1098 // Tests that the SessionsSyncManager can handle a remote client deleting
1099 // sync nodes that belong to this local session.
1100 TEST_F(SessionsSyncManagerTest, ProcessRemoteDeleteOfLocalSession) {
1101   syncer::SyncChangeList out;
1102   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1103   ASSERT_EQ(2U, out.size());
1104   sync_pb::EntitySpecifics entity(out[0].sync_data().GetSpecifics());
1105   SyncData d(SyncData::CreateRemoteData(1, entity, base::Time()));
1106   SetSyncData(syncer::SyncDataList(&d, &d + 1));
1107   out.clear();
1108
1109   syncer::SyncChangeList changes;
1110   changes.push_back(
1111       MakeRemoteChange(1, entity.session(), SyncChange::ACTION_DELETE));
1112   manager()->ProcessSyncChanges(FROM_HERE, changes);
1113   EXPECT_TRUE(manager()->local_tab_pool_out_of_sync_);
1114   EXPECT_TRUE(out.empty());  // ChangeProcessor shouldn't see any activity.
1115
1116   // This should trigger repair of the TabNodePool.
1117   const GURL foo1("http://foo/1");
1118   AddTab(browser(), foo1);
1119   EXPECT_FALSE(manager()->local_tab_pool_out_of_sync_);
1120
1121   // AddTab triggers two notifications, one for the tab insertion and one for
1122   // committing the NavigationEntry. The first notification results in a tab
1123   // we don't associate although we do update the header node.  The second
1124   // notification triggers association of the tab, and the subsequent window
1125   // update.  So we should see 4 changes at the SyncChangeProcessor.
1126   ASSERT_EQ(4U, out.size());
1127
1128   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[0].change_type());
1129   ASSERT_TRUE(out[0].sync_data().GetSpecifics().session().has_header());
1130   EXPECT_EQ(SyncChange::ACTION_ADD, out[1].change_type());
1131   int tab_node_id = out[1].sync_data().GetSpecifics().session().tab_node_id();
1132   EXPECT_EQ(TabNodePool2::TabIdToTag(
1133                 manager()->current_machine_tag(), tab_node_id),
1134             out[1].sync_data().GetTag());
1135   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1136   ASSERT_TRUE(out[2].sync_data().GetSpecifics().session().has_tab());
1137   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[3].change_type());
1138   ASSERT_TRUE(out[3].sync_data().GetSpecifics().session().has_header());
1139
1140   // Verify the actual content.
1141   const sync_pb::SessionHeader& session_header =
1142       out[3].sync_data().GetSpecifics().session().header();
1143   ASSERT_EQ(1, session_header.window_size());
1144   EXPECT_EQ(1, session_header.window(0).tab_size());
1145   const sync_pb::SessionTab& tab1 =
1146       out[2].sync_data().GetSpecifics().session().tab();
1147   ASSERT_EQ(1, tab1.navigation_size());
1148   EXPECT_EQ(foo1.spec(), tab1.navigation(0).virtual_url());
1149
1150   // Verify TabNodePool integrity.
1151   EXPECT_EQ(1U, manager()->local_tab_pool_.Capacity());
1152   EXPECT_TRUE(manager()->local_tab_pool_.Empty());
1153
1154   // Verify TabLinks.
1155   SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1156   ASSERT_EQ(1U, tab_map.size());
1157   int tab_id = out[2].sync_data().GetSpecifics().session().tab().tab_id();
1158   EXPECT_EQ(tab_node_id, tab_map.find(tab_id)->second->tab_node_id());
1159 }
1160
1161 // Test that receiving a session delete from sync removes the session
1162 // from tracking.
1163 TEST_F(SessionsSyncManagerTest, ProcessForeignDelete) {
1164   InitWithNoSyncData();
1165   SessionID::id_type n[] = {5};
1166   std::vector<sync_pb::SessionSpecifics> tabs1;
1167   std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1168   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1169       "tag1", tab_list, &tabs1));
1170
1171   syncer::SyncChangeList changes;
1172   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1173   AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1174   manager()->ProcessSyncChanges(FROM_HERE, changes);
1175
1176   std::vector<const SyncedSession*> foreign_sessions;
1177   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1178   ASSERT_EQ(1U, foreign_sessions.size());
1179
1180   changes.clear();
1181   foreign_sessions.clear();
1182   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1183   manager()->ProcessSyncChanges(FROM_HERE, changes);
1184
1185   EXPECT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1186 }
1187
1188 // TODO(shashishekhar): "Move this to TabNodePool unittests."
1189 TEST_F(SessionsSyncManagerTest, SaveUnassociatedNodesForReassociation) {
1190   syncer::SyncChangeList changes;
1191   InitWithNoSyncData();
1192
1193   std::string local_tag = manager()->current_machine_tag();
1194   // Create a free node and then dissassociate sessions so that it ends up
1195   // unassociated.
1196   manager()->local_tab_pool_.GetFreeTabNode(&changes);
1197
1198   // Update the tab_id of the node, so that it is considered a valid
1199   // unassociated node otherwise it will be mistaken for a corrupted node and
1200   // will be deleted before being added to the tab node pool.
1201   sync_pb::EntitySpecifics entity(changes[0].sync_data().GetSpecifics());
1202   entity.mutable_session()->mutable_tab()->set_tab_id(1);
1203   SyncData d(SyncData::CreateRemoteData(1, entity, base::Time()));
1204   syncer::SyncDataList in(&d, &d + 1);
1205   changes.clear();
1206   SessionsSyncManager manager2(profile(), this, NewDummyRouter());
1207   syncer::SyncMergeResult result = manager2.MergeDataAndStartSyncing(
1208       syncer::SESSIONS, in,
1209       scoped_ptr<syncer::SyncChangeProcessor>(
1210           new TestSyncProcessorStub(&changes)),
1211       scoped_ptr<syncer::SyncErrorFactory>(
1212           new syncer::SyncErrorFactoryMock()));
1213   ASSERT_FALSE(result.error().IsSet());
1214   EXPECT_TRUE(FilterOutLocalHeaderChanges(&changes)->empty());
1215 }
1216
1217 TEST_F(SessionsSyncManagerTest, MergeDeletesCorruptNode) {
1218   syncer::SyncChangeList changes;
1219   InitWithNoSyncData();
1220
1221   std::string local_tag = manager()->current_machine_tag();
1222   int tab_node_id = manager()->local_tab_pool_.GetFreeTabNode(&changes);
1223   SyncData d(SyncData::CreateRemoteData(
1224       1, changes[0].sync_data().GetSpecifics(), base::Time()));
1225   syncer::SyncDataList in(&d, &d + 1);
1226   changes.clear();
1227   TearDown();
1228   SetUp();
1229   InitWithSyncDataTakeOutput(in, &changes);
1230   EXPECT_EQ(1U, FilterOutLocalHeaderChanges(&changes)->size());
1231   EXPECT_EQ(SyncChange::ACTION_DELETE, changes[0].change_type());
1232   EXPECT_EQ(TabNodePool2::TabIdToTag(local_tag, tab_node_id),
1233             changes[0].sync_data().GetTag());
1234 }
1235
1236 // Test that things work if a tab is initially ignored.
1237 TEST_F(SessionsSyncManagerTest, AssociateWindowsDontReloadTabs) {
1238   syncer::SyncChangeList out;
1239   // Go to a URL that is ignored by session syncing.
1240   AddTab(browser(), GURL("chrome://preferences/"));
1241   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1242   ASSERT_EQ(2U, out.size());  // Header add and update.
1243   EXPECT_EQ(
1244       0,
1245       out[1].sync_data().GetSpecifics().session().header().window_size());
1246   out.clear();
1247
1248   // Go to a sync-interesting URL.
1249   NavigateAndCommitActiveTab(GURL("http://foo2"));
1250
1251   EXPECT_EQ(3U, out.size());  // Tab add, update, and header update.
1252
1253   EXPECT_TRUE(StartsWithASCII(out[0].sync_data().GetTag(),
1254                               manager()->current_machine_tag(), true));
1255   EXPECT_EQ(manager()->current_machine_tag(),
1256             out[0].sync_data().GetSpecifics().session().session_tag());
1257   EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1258
1259   EXPECT_TRUE(StartsWithASCII(out[1].sync_data().GetTag(),
1260                               manager()->current_machine_tag(), true));
1261   EXPECT_EQ(manager()->current_machine_tag(),
1262             out[1].sync_data().GetSpecifics().session().session_tag());
1263   EXPECT_TRUE(out[1].sync_data().GetSpecifics().session().has_tab());
1264   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[1].change_type());
1265
1266   EXPECT_TRUE(out[2].IsValid());
1267   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[2].change_type());
1268   const SyncData data(out[2].sync_data());
1269   EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1270   const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1271   EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1272   EXPECT_TRUE(specifics.has_header());
1273   const sync_pb::SessionHeader& header_s = specifics.header();
1274   EXPECT_EQ(1, header_s.window_size());
1275   EXPECT_EQ(1, header_s.window(0).tab_size());
1276 }
1277
1278 // Tests that the SyncSessionManager responds to local tab events properly.
1279 TEST_F(SessionsSyncManagerTest, OnLocalTabModified) {
1280   syncer::SyncChangeList out;
1281   // Init with no local data, relies on MergeLocalSessionNoTabs.
1282   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1283   ASSERT_FALSE(manager()->current_machine_tag().empty());
1284   ASSERT_EQ(2U, out.size());
1285
1286   // Copy the original header.
1287   sync_pb::EntitySpecifics header(out[0].sync_data().GetSpecifics());
1288   out.clear();
1289
1290   const GURL foo1("http://foo/1");
1291   const GURL foo2("http://foo/2");
1292   const GURL bar1("http://bar/1");
1293   const GURL bar2("http://bar/2");
1294   AddTab(browser(), foo1);
1295   NavigateAndCommitActiveTab(foo2);
1296   AddTab(browser(), bar1);
1297   NavigateAndCommitActiveTab(bar2);
1298
1299   // One add, one update for each AddTab.
1300   // One update for each NavigateAndCommit.
1301   // = 6 total tab updates.
1302   // One header update corresponding to each of those.
1303   // = 6 total header updates.
1304   // 12 total updates.
1305   ASSERT_EQ(12U, out.size());
1306
1307   // Verify the tab node creations and updates to ensure the SyncProcessor
1308   // sees the right operations.
1309   for (int i = 0; i < 12; i++) {
1310     SCOPED_TRACE(i);
1311     EXPECT_TRUE(out[i].IsValid());
1312     const SyncData data(out[i].sync_data());
1313     EXPECT_TRUE(StartsWithASCII(data.GetTag(),
1314                                 manager()->current_machine_tag(), true));
1315     const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1316     EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1317     if (i % 6 == 0) {
1318       // First thing on an AddTab is a no-op header update for parented tab.
1319       EXPECT_EQ(header.SerializeAsString(),
1320                 data.GetSpecifics().SerializeAsString());
1321       EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1322     } else if (i % 6 == 1) {
1323       // Next, the TabNodePool should create the tab node.
1324       EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1325       EXPECT_EQ(TabNodePool2::TabIdToTag(
1326                     manager()->current_machine_tag(),
1327                     data.GetSpecifics().session().tab_node_id()),
1328                 data.GetTag());
1329     } else if (i % 6 == 2) {
1330       // Then we see the tab update to the URL.
1331       EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1332       EXPECT_EQ(TabNodePool2::TabIdToTag(
1333                     manager()->current_machine_tag(),
1334                     data.GetSpecifics().session().tab_node_id()),
1335                 data.GetTag());
1336       ASSERT_TRUE(specifics.has_tab());
1337     } else if (i % 6 == 3) {
1338       // The header needs to be updated to reflect the new window state.
1339       EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1340       EXPECT_TRUE(specifics.has_header());
1341     } else if (i % 6 == 4) {
1342       // Now we move on to NavigateAndCommit.  Update the tab.
1343       EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1344       EXPECT_EQ(TabNodePool2::TabIdToTag(
1345                     manager()->current_machine_tag(),
1346                     data.GetSpecifics().session().tab_node_id()),
1347                 data.GetTag());
1348       ASSERT_TRUE(specifics.has_tab());
1349     } else if (i % 6 == 5) {
1350       // The header needs to be updated to reflect the new window state.
1351       EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1352       ASSERT_TRUE(specifics.has_header());
1353       header = data.GetSpecifics();
1354     }
1355   }
1356
1357   // Verify the actual content to ensure sync sees the right data.
1358   // When it's all said and done, the header should reflect two tabs.
1359   const sync_pb::SessionHeader& session_header = header.session().header();
1360   ASSERT_EQ(1, session_header.window_size());
1361   EXPECT_EQ(2, session_header.window(0).tab_size());
1362
1363   // ASSERT_TRUEs above allow us to dive in freely here.
1364   // Verify first tab.
1365   const sync_pb::SessionTab& tab1_1 =
1366       out[2].sync_data().GetSpecifics().session().tab();
1367   ASSERT_EQ(1, tab1_1.navigation_size());
1368   EXPECT_EQ(foo1.spec(), tab1_1.navigation(0).virtual_url());
1369   const sync_pb::SessionTab& tab1_2 =
1370       out[4].sync_data().GetSpecifics().session().tab();
1371   ASSERT_EQ(2, tab1_2.navigation_size());
1372   EXPECT_EQ(foo1.spec(), tab1_2.navigation(0).virtual_url());
1373   EXPECT_EQ(foo2.spec(), tab1_2.navigation(1).virtual_url());
1374
1375   // Verify second tab.
1376   const sync_pb::SessionTab& tab2_1 =
1377       out[8].sync_data().GetSpecifics().session().tab();
1378   ASSERT_EQ(1, tab2_1.navigation_size());
1379   EXPECT_EQ(bar1.spec(), tab2_1.navigation(0).virtual_url());
1380   const sync_pb::SessionTab& tab2_2 =
1381       out[10].sync_data().GetSpecifics().session().tab();
1382   ASSERT_EQ(2, tab2_2.navigation_size());
1383   EXPECT_EQ(bar1.spec(), tab2_2.navigation(0).virtual_url());
1384   EXPECT_EQ(bar2.spec(), tab2_2.navigation(1).virtual_url());
1385 }
1386
1387 // Ensure model association associates the pre-existing tabs.
1388 TEST_F(SessionsSyncManagerTest, MergeLocalSessionExistingTabs) {
1389   AddTab(browser(), GURL("http://foo1"));
1390   NavigateAndCommitActiveTab(GURL("http://foo2"));  // Adds back entry.
1391   AddTab(browser(), GURL("http://bar1"));
1392   NavigateAndCommitActiveTab(GURL("http://bar2"));  // Adds back entry.
1393
1394   syncer::SyncChangeList out;
1395   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1396   ASSERT_EQ(6U, out.size());
1397
1398   // Check that this machine's data is not included in the foreign windows.
1399   std::vector<const SyncedSession*> foreign_sessions;
1400   ASSERT_FALSE(manager()->GetAllForeignSessions(&foreign_sessions));
1401
1402   // Verify the header.
1403   EXPECT_TRUE(out[0].IsValid());
1404   EXPECT_EQ(SyncChange::ACTION_ADD, out[0].change_type());
1405   const SyncData data(out[0].sync_data());
1406   EXPECT_EQ(manager()->current_machine_tag(), data.GetTag());
1407   const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1408   EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1409   EXPECT_TRUE(specifics.has_header());
1410   const sync_pb::SessionHeader& header_s = specifics.header();
1411   EXPECT_TRUE(header_s.has_device_type());
1412   EXPECT_EQ(GetLocalDeviceInfo()->client_name(), header_s.client_name());
1413   EXPECT_EQ(0, header_s.window_size());
1414
1415   // Verify the tab node creations and updates with content.
1416   for (int i = 1; i < 5; i++) {
1417     EXPECT_TRUE(out[i].IsValid());
1418     const SyncData data(out[i].sync_data());
1419     EXPECT_TRUE(StartsWithASCII(data.GetTag(),
1420                                 manager()->current_machine_tag(), true));
1421     const sync_pb::SessionSpecifics& specifics(data.GetSpecifics().session());
1422     EXPECT_EQ(manager()->current_machine_tag(), specifics.session_tag());
1423     if (i % 2 == 1) {
1424       EXPECT_EQ(SyncChange::ACTION_ADD, out[i].change_type());
1425     } else {
1426       EXPECT_EQ(SyncChange::ACTION_UPDATE, out[i].change_type());
1427       EXPECT_TRUE(specifics.has_tab());
1428     }
1429   }
1430
1431   // Verify the header was updated to reflect new window state.
1432   EXPECT_TRUE(out[5].IsValid());
1433   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1434   const SyncData data_2(out[5].sync_data());
1435   EXPECT_EQ(manager()->current_machine_tag(), data_2.GetTag());
1436   const sync_pb::SessionSpecifics& specifics2(data_2.GetSpecifics().session());
1437   EXPECT_EQ(manager()->current_machine_tag(), specifics2.session_tag());
1438   EXPECT_TRUE(specifics2.has_header());
1439   const sync_pb::SessionHeader& header_s2 = specifics2.header();
1440   EXPECT_EQ(1, header_s2.window_size());
1441
1442   // Verify TabLinks.
1443   SessionsSyncManager::TabLinksMap tab_map = manager()->local_tab_map_;
1444   ASSERT_EQ(2U, tab_map.size());
1445   // Tabs are ordered by sessionid in tab_map, so should be able to traverse
1446   // the tree based on order of tabs created
1447   SessionsSyncManager::TabLinksMap::iterator iter = tab_map.begin();
1448   ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1449   EXPECT_EQ(GURL("http://foo1"), iter->second->tab()->
1450           GetEntryAtIndex(0)->GetVirtualURL());
1451   EXPECT_EQ(GURL("http://foo2"), iter->second->tab()->
1452           GetEntryAtIndex(1)->GetVirtualURL());
1453   iter++;
1454   ASSERT_EQ(2, iter->second->tab()->GetEntryCount());
1455   EXPECT_EQ(GURL("http://bar1"), iter->second->tab()->
1456       GetEntryAtIndex(0)->GetVirtualURL());
1457   EXPECT_EQ(GURL("http://bar2"), iter->second->tab()->
1458       GetEntryAtIndex(1)->GetVirtualURL());
1459 }
1460
1461 // Test garbage collection of stale foreign sessions.
1462 TEST_F(SessionsSyncManagerTest, DoGarbageCollection) {
1463   // Fill two instances of session specifics with a foreign session's data.
1464   std::string tag1 = "tag1";
1465   SessionID::id_type n1[] = {5, 10, 13, 17};
1466   std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1467   std::vector<sync_pb::SessionSpecifics> tabs1;
1468   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1469       tag1, tab_list1, &tabs1));
1470   std::string tag2 = "tag2";
1471   SessionID::id_type n2[] = {8, 15, 18, 20};
1472   std::vector<SessionID::id_type> tab_list2(n2, n2 + arraysize(n2));
1473   std::vector<sync_pb::SessionSpecifics> tabs2;
1474   sync_pb::SessionSpecifics meta2(helper()->BuildForeignSession(
1475       tag2, tab_list2, &tabs2));
1476   // Set the modification time for tag1 to be 21 days ago, tag2 to 5 days ago.
1477   base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1478   base::Time tag2_time = base::Time::Now() - base::TimeDelta::FromDays(5);
1479
1480   syncer::SyncDataList foreign_data;
1481   sync_pb::EntitySpecifics entity1, entity2;
1482   entity1.mutable_session()->CopyFrom(meta);
1483   entity2.mutable_session()->CopyFrom(meta2);
1484   foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time));
1485   foreign_data.push_back(SyncData::CreateRemoteData(1, entity2, tag2_time));
1486   AddTabsToSyncDataList(tabs1, &foreign_data);
1487   AddTabsToSyncDataList(tabs2, &foreign_data);
1488
1489   syncer::SyncChangeList output;
1490   InitWithSyncDataTakeOutput(foreign_data, &output);
1491   ASSERT_EQ(2U, output.size());
1492   output.clear();
1493
1494   // Check that the foreign session was associated and retrieve the data.
1495   std::vector<const SyncedSession*> foreign_sessions;
1496   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1497   ASSERT_EQ(2U, foreign_sessions.size());
1498   foreign_sessions.clear();
1499
1500   // Now garbage collect and verify the non-stale session is still there.
1501   manager()->DoGarbageCollection();
1502   ASSERT_EQ(5U, output.size());
1503   EXPECT_EQ(SyncChange::ACTION_DELETE, output[0].change_type());
1504   const SyncData data(output[0].sync_data());
1505   EXPECT_EQ(tag1, data.GetTag());
1506   for (int i = 1; i < 5; i++) {
1507     EXPECT_EQ(SyncChange::ACTION_DELETE, output[i].change_type());
1508     const SyncData data(output[i].sync_data());
1509     EXPECT_EQ(TabNodePool2::TabIdToTag(tag1, i), data.GetTag());
1510   }
1511
1512   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1513   ASSERT_EQ(1U, foreign_sessions.size());
1514   std::vector<std::vector<SessionID::id_type> > session_reference;
1515   session_reference.push_back(tab_list2);
1516   helper()->VerifySyncedSession(tag2, session_reference,
1517                                 *(foreign_sessions[0]));
1518 }
1519
1520 // Test that an update to a previously considered "stale" session,
1521 // prior to garbage collection, will save the session from deletion.
1522 TEST_F(SessionsSyncManagerTest, GarbageCollectionHonoursUpdate) {
1523   std::string tag1 = "tag1";
1524   SessionID::id_type n1[] = {5, 10, 13, 17};
1525   std::vector<SessionID::id_type> tab_list1(n1, n1 + arraysize(n1));
1526   std::vector<sync_pb::SessionSpecifics> tabs1;
1527   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1528       tag1, tab_list1, &tabs1));
1529   syncer::SyncDataList foreign_data;
1530   sync_pb::EntitySpecifics entity1;
1531   base::Time tag1_time = base::Time::Now() - base::TimeDelta::FromDays(21);
1532   entity1.mutable_session()->CopyFrom(meta);
1533   foreign_data.push_back(SyncData::CreateRemoteData(1, entity1, tag1_time));
1534   AddTabsToSyncDataList(tabs1, &foreign_data);
1535   syncer::SyncChangeList output;
1536   InitWithSyncDataTakeOutput(foreign_data, &output);
1537   ASSERT_EQ(2U, output.size());
1538
1539   // Update to a non-stale time.
1540   sync_pb::EntitySpecifics update_entity;
1541   update_entity.mutable_session()->CopyFrom(tabs1[0]);
1542   syncer::SyncChangeList changes;
1543   changes.push_back(syncer::SyncChange(
1544       FROM_HERE,
1545       SyncChange::ACTION_UPDATE,
1546       syncer::SyncData::CreateRemoteData(1, update_entity,
1547                                          base::Time::Now())));
1548   manager()->ProcessSyncChanges(FROM_HERE, changes);
1549
1550   // Check that the foreign session was associated and retrieve the data.
1551   std::vector<const SyncedSession*> foreign_sessions;
1552   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1553   ASSERT_EQ(1U, foreign_sessions.size());
1554   foreign_sessions.clear();
1555
1556   // Verify the now non-stale session does not get deleted.
1557   manager()->DoGarbageCollection();
1558   ASSERT_TRUE(manager()->GetAllForeignSessions(&foreign_sessions));
1559   ASSERT_EQ(1U, foreign_sessions.size());
1560   std::vector<std::vector<SessionID::id_type> > session_reference;
1561   session_reference.push_back(tab_list1);
1562   helper()->VerifySyncedSession(
1563       tag1, session_reference, *(foreign_sessions[0]));
1564 }
1565
1566 // Test that swapping WebContents for a tab is properly observed and handled
1567 // by the SessionsSyncManager.
1568 TEST_F(SessionsSyncManagerTest, CheckPrerenderedWebContentsSwap) {
1569   AddTab(browser(), GURL("http://foo1"));
1570   NavigateAndCommitActiveTab(GURL("http://foo2"));
1571
1572   syncer::SyncChangeList out;
1573   InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
1574   ASSERT_EQ(4U, out.size());  // Header, tab ADD, tab UPDATE, header UPDATE.
1575
1576   // To simulate WebContents swap during prerendering, create new WebContents
1577   // and swap with old WebContents.
1578   scoped_ptr<content::WebContents> old_web_contents;
1579   old_web_contents.reset(browser()->tab_strip_model()->GetActiveWebContents());
1580
1581   // Create new WebContents, with the required tab helpers.
1582   WebContents* new_web_contents = WebContents::CreateWithSessionStorage(
1583       WebContents::CreateParams(profile()),
1584       old_web_contents->GetController().GetSessionStorageNamespaceMap());
1585   SessionTabHelper::CreateForWebContents(new_web_contents);
1586   TabContentsSyncedTabDelegate::CreateForWebContents(new_web_contents);
1587   new_web_contents->GetController()
1588       .CopyStateFrom(old_web_contents->GetController());
1589
1590   // Swap the WebContents.
1591   int index = browser()->tab_strip_model()->GetIndexOfWebContents(
1592       old_web_contents.get());
1593   browser()->tab_strip_model()->ReplaceWebContentsAt(index, new_web_contents);
1594
1595   ASSERT_EQ(9U, out.size());
1596   EXPECT_EQ(SyncChange::ACTION_ADD, out[4].change_type());
1597   EXPECT_EQ(SyncChange::ACTION_UPDATE, out[5].change_type());
1598
1599   // Navigate away.
1600   NavigateAndCommitActiveTab(GURL("http://bar2"));
1601
1602   // Delete old WebContents. This should not crash.
1603   old_web_contents.reset();
1604
1605   // Try more navigations and verify output size. This can also reveal
1606   // bugs (leaks) on memcheck bots if the SessionSyncManager
1607   // didn't properly clean up the tab pool or session tracker.
1608   NavigateAndCommitActiveTab(GURL("http://bar3"));
1609
1610   AddTab(browser(), GURL("http://bar4"));
1611   NavigateAndCommitActiveTab(GURL("http://bar5"));
1612   ASSERT_EQ(19U, out.size());
1613 }
1614
1615 namespace {
1616 class SessionNotificationObserver : public content::NotificationObserver {
1617  public:
1618   SessionNotificationObserver() : notified_of_update_(false),
1619                                   notified_of_refresh_(false) {
1620     registrar_.Add(this, chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
1621                    content::NotificationService::AllSources());
1622     registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
1623                    content::NotificationService::AllSources());
1624   }
1625   virtual void Observe(int type,
1626                        const content::NotificationSource& source,
1627                        const content::NotificationDetails& details) OVERRIDE {
1628     switch (type) {
1629       case chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED:
1630         notified_of_update_ = true;
1631         break;
1632       case chrome::NOTIFICATION_SYNC_REFRESH_LOCAL:
1633         notified_of_refresh_ = true;
1634         break;
1635       default:
1636         NOTREACHED();
1637         break;
1638     }
1639   }
1640   bool notified_of_update() const { return notified_of_update_; }
1641   bool notified_of_refresh() const { return notified_of_refresh_; }
1642   void Reset() {
1643     notified_of_update_ = false;
1644     notified_of_refresh_ = false;
1645   }
1646  private:
1647   content::NotificationRegistrar registrar_;
1648   bool notified_of_update_;
1649   bool notified_of_refresh_;
1650 };
1651 }  // namespace
1652
1653 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when processing
1654 // sync changes.
1655 TEST_F(SessionsSyncManagerTest, NotifiedOfUpdates) {
1656   SessionNotificationObserver observer;
1657   ASSERT_FALSE(observer.notified_of_update());
1658   InitWithNoSyncData();
1659
1660   SessionID::id_type n[] = {5};
1661   std::vector<sync_pb::SessionSpecifics> tabs1;
1662   std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1663   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1664       "tag1", tab_list, &tabs1));
1665
1666   syncer::SyncChangeList changes;
1667   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1668   manager()->ProcessSyncChanges(FROM_HERE, changes);
1669   EXPECT_TRUE(observer.notified_of_update());
1670
1671   changes.clear();
1672   observer.Reset();
1673   AddTabsToChangeList(tabs1, SyncChange::ACTION_ADD, &changes);
1674   manager()->ProcessSyncChanges(FROM_HERE, changes);
1675   EXPECT_TRUE(observer.notified_of_update());
1676
1677   changes.clear();
1678   observer.Reset();
1679   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_DELETE));
1680   manager()->ProcessSyncChanges(FROM_HERE, changes);
1681   EXPECT_TRUE(observer.notified_of_update());
1682 }
1683
1684 // Test that NOTIFICATION_FOREIGN_SESSION_UPDATED is sent when handling
1685 // local hide/removal of foreign session.
1686 TEST_F(SessionsSyncManagerTest, NotifiedOfLocalRemovalOfForeignSession) {
1687   InitWithNoSyncData();
1688   const std::string tag("tag1");
1689   SessionID::id_type n[] = {5};
1690   std::vector<sync_pb::SessionSpecifics> tabs1;
1691   std::vector<SessionID::id_type> tab_list(n, n + arraysize(n));
1692   sync_pb::SessionSpecifics meta(helper()->BuildForeignSession(
1693       tag, tab_list, &tabs1));
1694
1695   syncer::SyncChangeList changes;
1696   changes.push_back(MakeRemoteChange(1, meta, SyncChange::ACTION_ADD));
1697   manager()->ProcessSyncChanges(FROM_HERE, changes);
1698
1699   SessionNotificationObserver observer;
1700   ASSERT_FALSE(observer.notified_of_update());
1701   manager()->DeleteForeignSession(tag);
1702   ASSERT_TRUE(observer.notified_of_update());
1703 }
1704
1705 #if defined(OS_ANDROID) || defined(OS_IOS)
1706 // Tests that opening the other devices page triggers a session sync refresh.
1707 // This page only exists on mobile platforms today; desktop has a
1708 // search-enhanced NTP without other devices.
1709 TEST_F(SessionsSyncManagerTest, NotifiedOfRefresh) {
1710   SessionNotificationObserver observer;
1711   ASSERT_FALSE(observer.notified_of_refresh());
1712   InitWithNoSyncData();
1713   AddTab(browser(), GURL("http://foo1"));
1714   EXPECT_FALSE(observer.notified_of_refresh());
1715   NavigateAndCommitActiveTab(GURL("chrome://newtab/#open_tabs"));
1716   EXPECT_TRUE(observer.notified_of_refresh());
1717 }
1718 #endif  // defined(OS_ANDROID) || defined(OS_IOS)
1719
1720 }  // namespace browser_sync