// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/lacros/suggestion_service_lacros.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/test/test_future.h"
#include "chrome/browser/sync/session_sync_service_factory.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/crosapi/mojom/suggestion_service.mojom.h"
#include "components/sessions/core/serialized_navigation_entry_test_helper.h"
#include "components/sync_sessions/open_tabs_ui_delegate.h"
#include "components/sync_sessions/session_sync_service.h"
#include "components/sync_sessions/synced_session.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr char kSessionName1[] = "test_session_name 1";
constexpr char kSessionName2[] = "test_session_name 2";
constexpr char kSessionTag1[] = "SessionTag1";
constexpr char kSessionTag2[] = "SessionTag2";
constexpr char kExampleURL1[] = "http://www.example.com/1";
constexpr char kExampleURL2[] = "http://www.example.com/2";
constexpr char16_t kTabTitle1[] = u"Tab Title 1";
constexpr char16_t kTabTitle2[] = u"Tab Title 2";
std::unique_ptr<sync_sessions::SyncedSession> CreateNewSession(
const std::string& session_name,
const std::string& session_tag,
syncer::DeviceInfo::FormFactor form_factor) {
auto session = std::make_unique<sync_sessions::SyncedSession>();
auto window = std::make_unique<sync_sessions::SyncedSessionWindow>();
auto tab = std::make_unique<sessions::SessionTab>();
session->SetSessionName(session_name);
session->SetDeviceTypeAndFormFactor(sync_pb::SyncEnums::TYPE_UNSET,
form_factor);
window->wrapped_window.tabs.push_back(std::move(tab));
session->windows[SessionID::NewUnique()] = std::move(window);
session->SetSessionTag(session_tag);
return session;
}
class MockSessionSyncService : public sync_sessions::SessionSyncService {
public:
MockSessionSyncService() = default;
~MockSessionSyncService() override = default;
MOCK_METHOD(syncer::GlobalIdMapper*,
GetGlobalIdMapper,
(),
(const, override));
MOCK_METHOD(sync_sessions::OpenTabsUIDelegate*,
GetOpenTabsUIDelegate,
(),
(override));
MOCK_METHOD(base::CallbackListSubscription,
SubscribeToForeignSessionsChanged,
(const base::RepeatingClosure& cb),
(override));
MOCK_METHOD(base::WeakPtr<syncer::DataTypeControllerDelegate>,
GetControllerDelegate,
());
};
class MockOpenTabsUIDelegate : public sync_sessions::OpenTabsUIDelegate {
public:
MockOpenTabsUIDelegate() {
foreign_sessions_owned_.push_back(CreateNewSession(
kSessionName1, kSessionTag1, syncer::DeviceInfo::FormFactor::kDesktop));
foreign_sessions_.push_back(foreign_sessions_owned_.back().get());
foreign_sessions_owned_.push_back(CreateNewSession(
kSessionName2, kSessionTag2, syncer::DeviceInfo::FormFactor::kPhone));
foreign_sessions_.push_back(foreign_sessions_owned_.back().get());
std::vector<std::unique_ptr<sessions::SessionTab>> session_tabs_one;
auto tab = std::make_unique<sessions::SessionTab>();
tab->timestamp = base::Time::Now();
tab->navigations.push_back(sessions::SerializedNavigationEntryTestHelper::
CreateNavigationForTest());
tab->navigations[0].set_timestamp(base::Time::Now());
tab->navigations[0].set_title(kTabTitle1);
tab->navigations[0].set_virtual_url(GURL(kExampleURL1));
session_tabs_one.push_back(std::move(tab));
std::vector<std::unique_ptr<sessions::SessionTab>> session_tabs_two;
tab = std::make_unique<sessions::SessionTab>();
tab->timestamp = base::Time::Now();
tab->navigations.push_back(sessions::SerializedNavigationEntryTestHelper::
CreateNavigationForTest());
tab->navigations[0].set_timestamp(base::Time::Now() + base::Minutes(5));
tab->navigations[0].set_title(kTabTitle2);
tab->navigations[0].set_virtual_url(GURL(kExampleURL2));
session_tabs_two.push_back(std::move(tab));
session_tabs_.emplace(kSessionTag1, std::move(session_tabs_one));
session_tabs_.emplace(kSessionTag2, std::move(session_tabs_two));
}
MOCK_METHOD(bool,
GetForeignTab,
(const std::string&,
const SessionID,
const sessions::SessionTab**));
MOCK_METHOD(void, DeleteForeignSession, (const std::string& tag));
MOCK_METHOD(std::vector<const sessions::SessionWindow*>,
GetForeignSession,
(const std::string&));
MOCK_METHOD(bool, GetLocalSession, (const sync_sessions::SyncedSession**));
bool GetAllForeignSessions(
std::vector<raw_ptr<const sync_sessions::SyncedSession,
VectorExperimental>>* sessions) override {
*sessions = foreign_sessions_;
base::ranges::sort(*sessions, std::greater(),
[](const sync_sessions::SyncedSession* session) {
return session->GetModifiedTime();
});
return !sessions->empty();
}
bool GetForeignSessionTabs(
const std::string& tag,
std::vector<const sessions::SessionTab*>* tabs) override {
auto it = session_tabs_.find(tag);
if (it != session_tabs_.end()) {
for (auto& tab : it->second) {
tabs->push_back(tab.get());
}
}
return true;
}
private:
std::vector<std::unique_ptr<sync_sessions::SyncedSession>>
foreign_sessions_owned_;
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
foreign_sessions_;
std::map<std::string, std::vector<std::unique_ptr<sessions::SessionTab>>>
session_tabs_;
};
std::unique_ptr<KeyedService> BuildMockSessionSyncService(
content::BrowserContext* context) {
return std::make_unique<testing::NiceMock<MockSessionSyncService>>();
}
} // namespace
class SuggestionServiceLacrosTest : public BrowserWithTestWindowTest {
public:
void SetUp() override {
BrowserWithTestWindowTest::SetUp();
auto* session_sync_service = static_cast<MockSessionSyncService*>(
SessionSyncServiceFactory::GetInstance()->GetForProfile(GetProfile()));
EXPECT_CALL(*session_sync_service, GetOpenTabsUIDelegate())
.WillRepeatedly(testing::Return(&open_tabs_delegate_));
}
void SetTestingFactory(content::BrowserContext* context) {
SessionSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
context, base::BindRepeating([](::content::BrowserContext* context)
-> std::unique_ptr<KeyedService> {
return std::make_unique<MockSessionSyncService>();
}));
}
TestingProfile* CreateProfile(const std::string& profile_name) override {
return profile_manager()->CreateTestingProfile(profile_name,
GetTestingFactories(), true);
}
TestingProfile::TestingFactories GetTestingFactories() override {
return {
TestingProfile::TestingFactory{
SessionSyncServiceFactory::GetInstance(),
base::BindRepeating(&BuildMockSessionSyncService)},
};
}
private:
MockOpenTabsUIDelegate open_tabs_delegate_;
};
// Test that SuggestionServiceLacros gets tabs properly.
TEST_F(SuggestionServiceLacrosTest, GetTabSuggestions) {
SuggestionServiceLacros suggestion_service_lacros;
base::test::TestFuture<std::vector<crosapi::mojom::TabSuggestionItemPtr>>
future;
suggestion_service_lacros.GetTabSuggestionItems(future.GetCallback());
const auto tabs = future.Take();
ASSERT_EQ(tabs.size(), 2u);
EXPECT_EQ(tabs[0]->title, base::UTF16ToUTF8(kTabTitle1));
EXPECT_EQ(tabs[0]->url, GURL(kExampleURL1));
EXPECT_EQ(tabs[0]->session_name, kSessionName1);
EXPECT_EQ(tabs[0]->form_factor,
crosapi::mojom::SuggestionDeviceFormFactor::kDesktop);
EXPECT_EQ(tabs[1]->title, base::UTF16ToUTF8(kTabTitle2));
EXPECT_EQ(tabs[1]->url, GURL(kExampleURL2));
EXPECT_EQ(tabs[1]->session_name, kSessionName2);
EXPECT_EQ(tabs[1]->form_factor,
crosapi::mojom::SuggestionDeviceFormFactor::kPhone);
}