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