#include "chrome/browser/ash/mahi/mahi_manager_impl.h"
#include <memory>
#include <string>
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/mahi/mahi_constants.h"
#include "ash/system/mahi/test/mock_mahi_media_app_content_manager.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/test/ash_test_base.h"
#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/unguessable_token.h"
#include "chrome/browser/ash/magic_boost/magic_boost_state_ash.h"
#include "chrome/browser/ash/mahi/mahi_cache_manager.h"
#include "chrome/browser/ash/mahi/web_contents/test_support/fake_mahi_web_contents_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/components/magic_boost/public/cpp/magic_boost_state.h"
#include "chromeos/components/mahi/public/cpp/mahi_media_app_content_manager.h"
#include "chromeos/components/mahi/public/cpp/mahi_web_contents_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/constants/chromeos_switches.h"
#include "chromeos/crosapi/mojom/mahi.mojom-forward.h"
#include "chromeos/crosapi/mojom/mahi.mojom.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/widget/widget.h"
namespace {
using base::test::RunOnceCallback;
using mahi::FakeMahiWebContentsManager;
using ::testing::_;
using ::testing::IsNull;
using ::testing::Return;
constexpr char kFakeSummary[] = "Fake summary";
constexpr char kFakeContent[] = "Test page content";
constexpr char kFakeElucidation[] = "Fake elucidation";
class FakeMahiProvider : public manta::MahiProvider {
public:
FakeMahiProvider(
scoped_refptr<network::SharedURLLoaderFactory> test_url_loader_factory,
signin::IdentityManager* identity_manager)
: MahiProvider(std::move(test_url_loader_factory), identity_manager) {}
void Summarize(const std::string& input,
const std::string& title,
const std::optional<std::string>& context,
const std::optional<std::string>& url,
manta::MantaGenericCallback callback) override {
++num_summarize_call_;
latest_summary_input_ = input;
latest_title_ = title;
latest_url_ = url;
std::move(callback).Run(base::Value::Dict().Set("outputData", kFakeSummary),
{manta::MantaStatusCode::kOk, "Status string ok"});
}
void Elucidate(const std::string& input,
const std::string& context,
const std::string& title,
const std::optional<std::string>& url,
manta::MantaGenericCallback callback) override {
latest_elucidation_input_ = input;
std::move(callback).Run(
base::Value::Dict().Set("outputData", kFakeElucidation),
{manta::MantaStatusCode::kOk, "Status string ok"});
}
int NumberOfSumarizeCall() { return num_summarize_call_; }
const std::string& latest_title() const { return latest_title_; }
const std::optional<std::string>& latest_url() const { return latest_url_; }
const std::string& latest_elucidation_input() const {
return latest_elucidation_input_;
}
const std::string& latest_summary_input() const {
return latest_summary_input_;
}
private:
int num_summarize_call_ = 0;
std::string latest_title_;
std::string latest_elucidation_input_;
std::string latest_summary_input_;
std::optional<std::string> latest_url_;
};
bool IsMahiNudgeShown() {
return ash::Shell::Get()->anchored_nudge_manager()->IsNudgeShown(
ash::mahi_constants::kMahiNudgeId);
}
}
namespace ash {
class MahiManagerImplTest : public NoSessionAshTestBase {
public:
MahiManagerImplTest()
: NoSessionAshTestBase(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
MahiManagerImplTest(const MahiManagerImplTest&) = delete;
MahiManagerImplTest& operator=(const MahiManagerImplTest&) = delete;
~MahiManagerImplTest() override = default;
void SetUp() override {
feature_list_.InitWithFeatures(
{chromeos::features::kMahi,
chromeos::features::kFeatureManagementMahi},
{});
NoSessionAshTestBase::SetUp();
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kMahiRestrictionsOverride);
magic_boost_state_ = std::make_unique<MagicBoostStateAsh>(
base::BindRepeating([]() { return static_cast<Profile*>(nullptr); }));
mahi_manager_impl_ = std::make_unique<MahiManagerImpl>();
mahi_manager_impl_->mahi_provider_ = CreateMahiProvider();
SimulateUserLogin(kRegularUserLoginInfo);
}
void TearDown() override {
mahi_manager_impl_.reset();
magic_boost_state_.reset();
NoSessionAshTestBase::TearDown();
}
void SetMahiEnabledByUserPref(bool enabled) {
Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
ash::prefs::kHmrEnabled, enabled);
}
FakeMahiProvider* GetMahiProvider() {
return static_cast<FakeMahiProvider*>(
mahi_manager_impl_->mahi_provider_.get());
}
mahi::FakeMahiWebContentsManager* GetFakeMahiWebContentsManager() {
return static_cast<FakeMahiWebContentsManager*>(
chromeos::MahiWebContentsManager::Get());
}
bool IsEnabled() const { return mahi_manager_impl_->IsEnabled(); }
crosapi::mojom::MahiPageInfoPtr CreatePageInfo(const std::string& url,
const std::u16string& title,
bool is_incognito = false) {
return crosapi::mojom::MahiPageInfo::New(
base::UnguessableToken(),
base::UnguessableToken(), GURL(url),
title,
gfx::ImageSkia(), true,
is_incognito);
}
MahiCacheManager* GetCacheManager() {
return mahi_manager_impl_->cache_manager_.get();
}
void NotifyRefreshAvailability(bool available) {
mahi_manager_impl_->NotifyRefreshAvailability(available);
}
void RequestContent(bool incognito = false,
const std::string& url = "http://url1.com/abc#skip") {
mahi_manager_impl_->SetCurrentFocusedPageInfo(
CreatePageInfo(url, u"Title of url1",
incognito));
mahi_manager_impl_->GetContent(base::DoNothing());
}
void UpdateCurrentSelectedText() {
mahi_manager_impl_->UpdateCurrentSelectedText();
}
void RequestSummary(bool incognito = false,
const std::string& url = "http://url1.com/abc#skip") {
mahi_manager_impl_->SetCurrentFocusedPageInfo(
CreatePageInfo(url, u"Title of url1",
incognito));
mahi_manager_impl_->GetSummary(base::DoNothing());
}
protected:
std::unique_ptr<FakeMahiProvider> CreateMahiProvider() {
return std::make_unique<FakeMahiProvider>(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_),
identity_test_env_.identity_manager());
}
std::unique_ptr<MagicBoostStateAsh> magic_boost_state_;
std::unique_ptr<MahiManagerImpl> mahi_manager_impl_;
base::test::ScopedFeatureList feature_list_;
testing::StrictMock<MockMahiMediaAppContentManager>
mock_mahi_media_app_content_manager_;
chromeos::ScopedMahiMediaAppContentManagerSetter
scoped_mahi_media_app_content_manager_{
&mock_mahi_media_app_content_manager_};
private:
mahi::FakeMahiWebContentsManager fake_mahi_web_contents_manager_;
chromeos::ScopedMahiWebContentsManagerOverride
scoped_mahi_web_contents_manager_{&fake_mahi_web_contents_manager_};
network::TestURLLoaderFactory test_url_loader_factory_;
signin::IdentityTestEnvironment identity_test_env_;
};
TEST_F(MahiManagerImplTest, CacheSavedForContentRequest) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestContent();
EXPECT_EQ(GetCacheManager()->size(), 1);
auto content = GetCacheManager()->GetPageContentForUrl("http://url1.com/abc");
EXPECT_EQ(base::UTF16ToUTF8(content), kFakeContent);
}
TEST_F(MahiManagerImplTest, NoContentCacheSavedForIncognitoPage) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestContent(true);
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary(false);
EXPECT_EQ(GetCacheManager()->size(), 1);
}
TEST_F(MahiManagerImplTest, NoContentCallWhenContentIsInCache) {
const std::u16string new_summary(u"new summary");
const std::u16string new_content(u"Page content");
GetCacheManager()->AddCacheForUrl(
"http://url1.com/abc#random",
MahiCacheManager::MahiData(
"http://url1.com/abc#skip", u"Title of url1",
new_content, std::nullopt,
new_summary, {}));
RequestContent();
auto content = GetCacheManager()->GetPageContentForUrl("http://url1.com/abc");
EXPECT_EQ(GetFakeMahiWebContentsManager()->GetNumberOfRequestContentCalls(),
0);
EXPECT_EQ(content, new_content);
}
TEST_F(MahiManagerImplTest, SendingTitleOnly) {
feature_list_.Reset();
feature_list_.InitWithFeatures(
{chromeos::features::kMahi,
chromeos::features::kFeatureManagementMahi},
{chromeos::features::kMahiSendingUrl});
RequestSummary();
EXPECT_EQ(GetMahiProvider()->latest_title(), "Title of url1");
EXPECT_FALSE(GetMahiProvider()->latest_url().has_value());
}
TEST_F(MahiManagerImplTest, SendingTitleAndUrl) {
feature_list_.Reset();
feature_list_.InitWithFeatures(
{chromeos::features::kMahi, chromeos::features::kMahiSendingUrl,
chromeos::features::kFeatureManagementMahi},
{});
RequestSummary();
EXPECT_TRUE(GetMahiProvider()->latest_url().has_value());
EXPECT_EQ(GetMahiProvider()->latest_url().value(),
"http://url1.com/abc#skip");
RequestSummary(false, "file:///media-app/example.pdf");
EXPECT_FALSE(GetMahiProvider()->latest_url().has_value());
}
TEST_F(MahiManagerImplTest, CacheSavedForSummaryRequest) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary();
EXPECT_EQ(GetCacheManager()->size(), 1);
auto summary = GetCacheManager()->GetSummaryForUrl("http://url1.com/abc");
EXPECT_EQ(GetMahiProvider()->NumberOfSumarizeCall(), 1);
EXPECT_TRUE(summary.has_value());
EXPECT_EQ(base::UTF16ToUTF8(summary.value()), kFakeSummary);
}
TEST_F(MahiManagerImplTest, NoSummaryCacheSavedForIncognitoPage) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary(true);
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary(false);
EXPECT_EQ(GetCacheManager()->size(), 1);
}
TEST_F(MahiManagerImplTest, NoSummaryCallWhenSummaryIsInCache) {
const std::u16string new_summary(u"new summary");
GetCacheManager()->AddCacheForUrl(
"http://url1.com/abc#random",
MahiCacheManager::MahiData(
"http://url1.com/abc#skip", u"Title of url1",
u"Page content", std::nullopt,
new_summary, {}));
RequestSummary();
auto summary = GetCacheManager()->GetSummaryForUrl("http://url1.com/abc");
EXPECT_EQ(GetMahiProvider()->NumberOfSumarizeCall(), 0);
EXPECT_TRUE(summary.has_value());
EXPECT_EQ(summary.value(), new_summary);
}
TEST_F(MahiManagerImplTest, ClearAllCacheWhenAllHistoryAreBeingCleared) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary();
EXPECT_EQ(GetCacheManager()->size(), 1);
mahi_manager_impl_->OnHistoryDeletions(
nullptr, history::DeletionInfo::ForAllHistory());
EXPECT_EQ(GetCacheManager()->size(), 0);
}
TEST_F(MahiManagerImplTest, ClearURLs) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary();
EXPECT_EQ(GetCacheManager()->size(), 1);
{
const auto kUrl1 = GURL("http://www.a.com");
const auto kUrl2 = GURL("http://www.b.com");
history::URLRows urls_to_delete = {history::URLRow(kUrl1),
history::URLRow(kUrl2)};
history::DeletionInfo deletion_info =
history::DeletionInfo::ForUrls(urls_to_delete, std::set<GURL>());
mahi_manager_impl_->OnHistoryDeletions(nullptr, deletion_info);
EXPECT_EQ(GetCacheManager()->size(), 1);
}
{
const auto kUrl1 = GURL("http://www.a.com");
const auto kUrl2 = GURL("http://url1.com/abc#should_delete");
history::URLRows urls_to_delete = {history::URLRow(kUrl1),
history::URLRow(kUrl2)};
history::DeletionInfo deletion_info =
history::DeletionInfo::ForUrls(urls_to_delete, std::set<GURL>());
mahi_manager_impl_->OnHistoryDeletions(nullptr, deletion_info);
EXPECT_EQ(GetCacheManager()->size(), 0);
}
}
TEST_F(MahiManagerImplTest, TurnOffSettingsClearCache) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary();
EXPECT_EQ(GetCacheManager()->size(), 1);
SetMahiEnabledByUserPref(false);
EXPECT_EQ(GetCacheManager()->size(), 0);
}
TEST_F(MahiManagerImplTest, ClearCacheSuccessfully) {
EXPECT_EQ(GetCacheManager()->size(), 0);
RequestSummary();
EXPECT_EQ(GetCacheManager()->size(), 1);
mahi_manager_impl_->ClearCache();
EXPECT_EQ(GetCacheManager()->size(), 0);
}
TEST_F(MahiManagerImplTest, SetMahiPrefOnLogin) {
for (bool mahi_enabled : {false, true}) {
SetMahiEnabledByUserPref(mahi_enabled);
ASSERT_EQ(IsEnabled(), mahi_enabled);
const AccountId user1_account_id =
Shell::Get()->session_controller()->GetActiveAccountId();
SimulateUserLogin({"other@user.test"});
SetMahiEnabledByUserPref(!mahi_enabled);
EXPECT_EQ(IsEnabled(), !mahi_enabled);
SwitchActiveUser(user1_account_id);
EXPECT_EQ(IsEnabled(), mahi_enabled);
ClearLogin();
SimulateUserLogin(user1_account_id);
}
}
TEST_F(MahiManagerImplTest, OnPreferenceChanged) {
for (bool mahi_enabled : {false, true, false}) {
SetMahiEnabledByUserPref(mahi_enabled);
EXPECT_EQ(IsEnabled(), mahi_enabled);
}
}
TEST_F(MahiManagerImplTest, ShowEducationalNudge) {
SetMahiEnabledByUserPref(false);
EXPECT_FALSE(IsMahiNudgeShown());
NotifyRefreshAvailability(false);
EXPECT_FALSE(IsMahiNudgeShown());
NotifyRefreshAvailability(true);
EXPECT_TRUE(IsMahiNudgeShown());
NotifyRefreshAvailability(false);
EXPECT_TRUE(IsMahiNudgeShown());
}
TEST_F(MahiManagerImplTest, GetElucidation) {
const std::u16string selected_text = u"test selected text";
chromeos::MahiWebContentsManager::Get()->SetSelectedText(selected_text);
base::test::TestFuture<std::u16string, chromeos::MahiResponseStatus>
test_future;
UpdateCurrentSelectedText();
mahi_manager_impl_->GetElucidation(test_future.GetCallback());
EXPECT_EQ(GetMahiProvider()->latest_elucidation_input(),
base::UTF16ToUTF8(selected_text));
EXPECT_EQ(test_future.Get<std::u16string>(),
base::UTF8ToUTF16(std::string(kFakeElucidation)));
}
TEST_F(MahiManagerImplTest, GetElucidationForMediaApp) {
std::string selected_text = "test PDF selected text";
EXPECT_CALL(mock_mahi_media_app_content_manager_, GetSelectedText)
.WillOnce(Return(selected_text));
EXPECT_CALL(mock_mahi_media_app_content_manager_, GetFileName(_))
.WillOnce(Return("test PDF file name"));
EXPECT_CALL(mock_mahi_media_app_content_manager_, GetContent(_, _))
.WillOnce(RunOnceCallback<1>(crosapi::mojom::MahiPageContent::New(
base::UnguessableToken::Create(), base::UnguessableToken::Create(),
u"test PDF content")));
mahi_manager_impl_->SetMediaAppPDFFocused();
base::test::TestFuture<std::u16string, chromeos::MahiResponseStatus>
test_future;
UpdateCurrentSelectedText();
mahi_manager_impl_->GetElucidation(test_future.GetCallback());
EXPECT_EQ(GetMahiProvider()->latest_elucidation_input(), selected_text);
EXPECT_EQ(test_future.Get<std::u16string>(),
base::UTF8ToUTF16(std::string(kFakeElucidation)));
}
TEST_F(MahiManagerImplTest, GetSummaryForSelectedText) {
const std::u16string selected_text = u"test selected text";
chromeos::MahiWebContentsManager::Get()->SetSelectedText(selected_text);
base::test::TestFuture<std::u16string, chromeos::MahiResponseStatus>
test_future;
mahi_manager_impl_->SetCurrentFocusedPageInfo(
CreatePageInfo("http://url1.com/abc#random", u"Title of url1",
false));
UpdateCurrentSelectedText();
mahi_manager_impl_->GetSummary(test_future.GetCallback());
EXPECT_EQ(GetMahiProvider()->latest_summary_input(),
base::UTF16ToUTF8(selected_text));
EXPECT_EQ(test_future.Get<std::u16string>(),
base::UTF8ToUTF16(std::string(kFakeSummary)));
EXPECT_EQ(GetCacheManager()->size(), 1);
}
}