1 // Copyright 2020 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/chromeos/phonehub/browser_tabs_model_provider_impl.h"
6 
7 #include "chromeos/components/multidevice/remote_device_test_util.h"
8 #include "chromeos/components/phonehub/fake_browser_tabs_metadata_fetcher.h"
9 #include "chromeos/components/phonehub/mutable_phone_model.h"
10 #include "chromeos/components/phonehub/phone_model_test_util.h"
11 #include "chromeos/services/multidevice_setup/public/cpp/fake_multidevice_setup_client.h"
12 #include "components/sync_sessions/open_tabs_ui_delegate.h"
13 #include "components/sync_sessions/session_sync_service.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 
17 namespace chromeos {
18 namespace phonehub {
19 
20 using testing::_;
21 using testing::Return;
22 
23 namespace {
24 
25 const char kPhoneNameOne[] = "Pixel";
26 const char kPhoneNameTwo[] = "Galaxy";
27 
28 class SessionSyncServiceMock : public sync_sessions::SessionSyncService {
29  public:
SessionSyncServiceMock()30   SessionSyncServiceMock() {}
~SessionSyncServiceMock()31   ~SessionSyncServiceMock() override {}
32 
33   MOCK_CONST_METHOD0(GetGlobalIdMapper, syncer::GlobalIdMapper*());
34   MOCK_METHOD0(GetOpenTabsUIDelegate, sync_sessions::OpenTabsUIDelegate*());
35   MOCK_METHOD1(SubscribeToForeignSessionsChanged,
36                std::unique_ptr<base::CallbackList<void()>::Subscription>(
37                    const base::RepeatingClosure& cb));
38   MOCK_METHOD0(ScheduleGarbageCollection, void());
39   MOCK_METHOD0(GetControllerDelegate,
40                base::WeakPtr<syncer::ModelTypeControllerDelegate>());
41   MOCK_METHOD1(ProxyTabsStateChanged,
42                void(syncer::DataTypeController::State state));
43   MOCK_METHOD1(SetSyncSessionsGUID, void(const std::string& guid));
44 };
45 
46 class OpenTabsUIDelegateMock : public sync_sessions::OpenTabsUIDelegate {
47  public:
OpenTabsUIDelegateMock()48   OpenTabsUIDelegateMock() {}
~OpenTabsUIDelegateMock()49   ~OpenTabsUIDelegateMock() override {}
50 
51   MOCK_METHOD1(
52       GetAllForeignSessions,
53       bool(std::vector<const sync_sessions::SyncedSession*>* sessions));
54   MOCK_METHOD3(GetForeignTab,
55                bool(const std::string& tag,
56                     const SessionID tab_id,
57                     const sessions::SessionTab** tab));
58   MOCK_METHOD1(DeleteForeignSession, void(const std::string& tag));
59   MOCK_METHOD2(GetForeignSession,
60                bool(const std::string& tag,
61                     std::vector<const sessions::SessionWindow*>* windows));
62   MOCK_METHOD2(GetForeignSessionTabs,
63                bool(const std::string& tag,
64                     std::vector<const sessions::SessionTab*>* tabs));
65   MOCK_METHOD1(GetLocalSession,
66                bool(const sync_sessions::SyncedSession** local));
67 };
68 
CreatePhoneDevice(const std::string & pii_name)69 multidevice::RemoteDeviceRef CreatePhoneDevice(const std::string& pii_name) {
70   multidevice::RemoteDeviceRefBuilder builder;
71   builder.SetPiiFreeName(pii_name);
72   return builder.Build();
73 }
74 
CreateNewSession(const std::string & session_name,const base::Time & session_time=base::Time::FromDoubleT (0))75 sync_sessions::SyncedSession* CreateNewSession(
76     const std::string& session_name,
77     const base::Time& session_time = base::Time::FromDoubleT(0)) {
78   sync_sessions::SyncedSession* session = new sync_sessions::SyncedSession();
79   session->session_name = session_name;
80   session->modified_time = session_time;
81   return session;
82 }
83 
84 }  // namespace
85 
86 class BrowserTabsModelProviderImplTest
87     : public testing::Test,
88       public BrowserTabsModelProvider::Observer {
89  public:
90   BrowserTabsModelProviderImplTest() = default;
91 
92   BrowserTabsModelProviderImplTest(const BrowserTabsModelProviderImplTest&) =
93       delete;
94   BrowserTabsModelProviderImplTest& operator=(
95       const BrowserTabsModelProviderImplTest&) = delete;
96   ~BrowserTabsModelProviderImplTest() override = default;
97 
OnBrowserTabsUpdated(bool is_sync_enabled,const std::vector<BrowserTabsModel::BrowserTabMetadata> & browser_tabs_metadata)98   void OnBrowserTabsUpdated(
99       bool is_sync_enabled,
100       const std::vector<BrowserTabsModel::BrowserTabMetadata>&
101           browser_tabs_metadata) override {
102     phone_model_.SetBrowserTabsModel(BrowserTabsModel(
103         /*is_tab_sync_enabled=*/is_sync_enabled, browser_tabs_metadata));
104   }
105 
106   // testing::Test:
SetUp()107   void SetUp() override {
108     ON_CALL(mock_session_sync_service_, GetOpenTabsUIDelegate())
109         .WillByDefault(Invoke(
110             this, &BrowserTabsModelProviderImplTest::open_tabs_ui_delegate));
111 
112     ON_CALL(open_tabs_ui_delegate_, GetAllForeignSessions(_))
113         .WillByDefault(Invoke(
114             this,
115             &BrowserTabsModelProviderImplTest::MockGetAllForeignSessions));
116 
117     ON_CALL(mock_session_sync_service_, SubscribeToForeignSessionsChanged(_))
118         .WillByDefault(Invoke(this, &BrowserTabsModelProviderImplTest::
119                                         MockSubscribeToForeignSessionsChanged));
120 
121     provider_ = std::make_unique<BrowserTabsModelProviderImpl>(
122         &fake_multidevice_setup_client_, &mock_session_sync_service_,
123         std::make_unique<FakeBrowserTabsMetadataFetcher>());
124     provider_->AddObserver(this);
125   }
126 
SetPiiFreeName(const std::string & pii_free_name)127   void SetPiiFreeName(const std::string& pii_free_name) {
128     fake_multidevice_setup_client_.SetHostStatusWithDevice(std::make_pair(
129         multidevice_setup::mojom::HostStatus::kEligibleHostExistsButNoHostSet,
130         CreatePhoneDevice(/*pii_name=*/pii_free_name)));
131   }
132 
133   std::unique_ptr<base::CallbackList<void()>::Subscription>
MockSubscribeToForeignSessionsChanged(const base::RepeatingClosure & cb)134   MockSubscribeToForeignSessionsChanged(const base::RepeatingClosure& cb) {
135     foreign_sessions_changed_callback_ = std::move(cb);
136     return nullptr;
137   }
138 
MockGetAllForeignSessions(std::vector<const sync_sessions::SyncedSession * > * sessions)139   bool MockGetAllForeignSessions(
140       std::vector<const sync_sessions::SyncedSession*>* sessions) {
141     if (sessions_) {
142       *sessions = *sessions_;
143       return !sessions->empty();
144     }
145     return false;
146   }
147 
open_tabs_ui_delegate()148   testing::NiceMock<OpenTabsUIDelegateMock>* open_tabs_ui_delegate() {
149     return enable_tab_sync_ ? &open_tabs_ui_delegate_ : nullptr;
150   }
151 
NotifySubscription()152   void NotifySubscription() { foreign_sessions_changed_callback_.Run(); }
153 
set_synced_sessions(std::vector<const sync_sessions::SyncedSession * > * sessions)154   void set_synced_sessions(
155       std::vector<const sync_sessions::SyncedSession*>* sessions) {
156     sessions_ = sessions;
157   }
158 
set_enable_tab_sync(bool is_enabled)159   void set_enable_tab_sync(bool is_enabled) { enable_tab_sync_ = is_enabled; }
160 
fake_browser_tabs_metadata_fetcher()161   FakeBrowserTabsMetadataFetcher* fake_browser_tabs_metadata_fetcher() {
162     return static_cast<FakeBrowserTabsMetadataFetcher*>(
163         provider_->browser_tabs_metadata_fetcher_.get());
164   }
165 
166   MutablePhoneModel phone_model_;
167   multidevice_setup::FakeMultiDeviceSetupClient fake_multidevice_setup_client_;
168   testing::NiceMock<SessionSyncServiceMock> mock_session_sync_service_;
169   std::unique_ptr<BrowserTabsModelProviderImpl> provider_;
170 
171   testing::NiceMock<OpenTabsUIDelegateMock> open_tabs_ui_delegate_;
172 
173   bool enable_tab_sync_ = true;
174   std::vector<const sync_sessions::SyncedSession*>* sessions_ = nullptr;
175   base::RepeatingClosure foreign_sessions_changed_callback_;
176 };
177 
TEST_F(BrowserTabsModelProviderImplTest,AttemptBrowserTabsModelUpdate)178 TEST_F(BrowserTabsModelProviderImplTest, AttemptBrowserTabsModelUpdate) {
179   // Test no Pii Free name despite sync being enabled.
180   set_enable_tab_sync(true);
181   set_synced_sessions(nullptr);
182   NotifySubscription();
183   EXPECT_FALSE(phone_model_.browser_tabs_model()->is_tab_sync_enabled());
184   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
185   EXPECT_FALSE(
186       fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
187 
188   // Set name of phone. Tests that OnHostStatusChanged causes name change.
189   SetPiiFreeName(kPhoneNameOne);
190 
191   // Test disabling tab sync with no browser tab metadata.
192   set_enable_tab_sync(false);
193   set_synced_sessions(nullptr);
194   NotifySubscription();
195   EXPECT_FALSE(phone_model_.browser_tabs_model()->is_tab_sync_enabled());
196   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
197   EXPECT_FALSE(
198       fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
199 
200   // Test enabling tab sync with no browser tab metadata.
201   set_enable_tab_sync(true);
202   set_synced_sessions(nullptr);
203   NotifySubscription();
204   EXPECT_TRUE(phone_model_.browser_tabs_model()->is_tab_sync_enabled());
205   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
206   EXPECT_FALSE(
207       fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
208 
209   // Test enabling tab sync with no matching pii name with session_name.
210   std::vector<const sync_sessions::SyncedSession*> sessions;
211   sync_sessions::SyncedSession* session = CreateNewSession(kPhoneNameTwo);
212   sessions.emplace_back(session);
213   set_enable_tab_sync(true);
214   set_synced_sessions(&sessions);
215   NotifySubscription();
216   EXPECT_TRUE(phone_model_.browser_tabs_model()->is_tab_sync_enabled());
217   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
218   EXPECT_FALSE(
219       fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
220 
221   // Test enabling tab sync with matching pii name with session_name, which
222   // will cause the |fake_browser_tabs_metadata_fetcher()| to have a pending
223   // callback.
224   sync_sessions::SyncedSession* new_session = CreateNewSession(kPhoneNameOne);
225   sessions.emplace_back(new_session);
226   set_enable_tab_sync(true);
227   set_synced_sessions(&sessions);
228   NotifySubscription();
229   EXPECT_TRUE(phone_model_.browser_tabs_model()->is_tab_sync_enabled());
230   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
231   EXPECT_TRUE(fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
232 
233   // Test that once |fake_browser_tabs_metadata_fetcher()| responds, the phone
234   // model will be appropriately updated.
235   std::vector<BrowserTabsModel::BrowserTabMetadata> metadata;
236   metadata.push_back(CreateFakeBrowserTabMetadata());
237   fake_browser_tabs_metadata_fetcher()->RespondToCurrentFetchAttempt(
238       std::move(metadata));
239   EXPECT_EQ(phone_model_.browser_tabs_model()->most_recent_tabs().size(), 1U);
240 }
241 
TEST_F(BrowserTabsModelProviderImplTest,ClearTabMetadataDuringMetadataFetch)242 TEST_F(BrowserTabsModelProviderImplTest, ClearTabMetadataDuringMetadataFetch) {
243   SetPiiFreeName(kPhoneNameOne);
244   sync_sessions::SyncedSession* new_session = CreateNewSession(kPhoneNameOne);
245   std::vector<const sync_sessions::SyncedSession*> sessions({new_session});
246 
247   set_enable_tab_sync(true);
248   set_synced_sessions(&sessions);
249   NotifySubscription();
250   EXPECT_TRUE(fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
251 
252   // Set to no synced sessions. Tab sync is still enabled.
253   set_synced_sessions(nullptr);
254   NotifySubscription();
255 
256   // Test that if the Browser tab metadata is cleared while a browser tab
257   // metadata fetch is in progress, the in progress callback will be cancelled.
258   std::vector<BrowserTabsModel::BrowserTabMetadata> metadata;
259   metadata.push_back(CreateFakeBrowserTabMetadata());
260   fake_browser_tabs_metadata_fetcher()->RespondToCurrentFetchAttempt(
261       std::move(metadata));
262   EXPECT_TRUE(phone_model_.browser_tabs_model()->most_recent_tabs().empty());
263 }
264 
TEST_F(BrowserTabsModelProviderImplTest,SessionCorrectlySelected)265 TEST_F(BrowserTabsModelProviderImplTest, SessionCorrectlySelected) {
266   SetPiiFreeName(kPhoneNameOne);
267   sync_sessions::SyncedSession* session_a =
268       CreateNewSession(kPhoneNameOne, base::Time::FromDoubleT(1));
269   sync_sessions::SyncedSession* session_b =
270       CreateNewSession(kPhoneNameOne, base::Time::FromDoubleT(3));
271   sync_sessions::SyncedSession* session_c =
272       CreateNewSession(kPhoneNameOne, base::Time::FromDoubleT(2));
273   sync_sessions::SyncedSession* session_d =
274       CreateNewSession(kPhoneNameTwo, base::Time::FromDoubleT(10));
275 
276   std::vector<const sync_sessions::SyncedSession*> sessions(
277       {session_a, session_b, session_c, session_d});
278 
279   set_enable_tab_sync(true);
280   set_synced_sessions(&sessions);
281   NotifySubscription();
282   EXPECT_TRUE(fake_browser_tabs_metadata_fetcher()->DoesPendingCallbackExist());
283 
284   // |session_b| should be the selected session because it is the has the same
285   // session_name as the set phone name and the latest modified time.
286   EXPECT_EQ(fake_browser_tabs_metadata_fetcher()->GetSession(), session_b);
287 }
288 
289 }  // namespace phonehub
290 }  // namespace chromeos
291