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