blob: 418ac7eb2fa41501579d56d640eb286e7c17ce8b [file] [log] [blame]
// Copyright 2020 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/updates/announcement_notification/announcement_notification_service.h"
#include <memory>
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/profiles/profile_attributes_init_params.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.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 "components/account_id/account_id.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::NiceMock;
using testing::Return;
namespace {
const char kProfileId[] = "dummy@gmail.com";
const char kRemoteUrl[] = "www.example.com";
class MockDelegate : public AnnouncementNotificationService::Delegate {
public:
MockDelegate() = default;
MockDelegate(const MockDelegate&) = delete;
MockDelegate& operator=(const MockDelegate&) = delete;
~MockDelegate() override = default;
MOCK_METHOD0(ShowNotification, void());
MOCK_METHOD0(IsFirstRun, bool());
};
class AnnouncementNotificationServiceTest : public testing::Test {
public:
AnnouncementNotificationServiceTest() = default;
AnnouncementNotificationServiceTest(
const AnnouncementNotificationServiceTest&) = delete;
AnnouncementNotificationServiceTest& operator=(
const AnnouncementNotificationServiceTest&) = delete;
~AnnouncementNotificationServiceTest() override = default;
protected:
AnnouncementNotificationService* service() {
DCHECK(service_) << "Call Init() first.";
return service_.get();
}
MockDelegate* delegate() { return delegate_; }
base::SimpleTestClock* clock() { return &clock_; }
int CurrentVersionPref() const {
return pref_service_->GetInteger(kCurrentVersionPrefName);
}
base::Time FirstRunTimePref() const {
return pref_service_->GetTime(kAnnouncementFirstRunTimePrefName);
}
base::Time SetFirstRunTimePref(const char* time_str) {
base::Time time;
EXPECT_TRUE(base::Time::FromString(time_str, &time));
pref_service_->SetTime(kAnnouncementFirstRunTimePrefName, time);
return time;
}
// Sets the current time used in test. Assume UTC time when time zone is not
// specified.
base::Time SetNow(const char* now_str) {
base::Time now;
EXPECT_TRUE(base::Time::FromUTCString(now_str, &now));
clock()->SetNow(now);
EXPECT_FALSE(now.is_null());
return now;
}
void Init(const std::map<std::string, std::string>& parameters,
bool enable_feature,
bool sign_in,
int current_version,
bool new_profile,
bool guest_profile = false) {
std::vector<base::test::FeatureRefAndParams> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
if (enable_feature)
enabled_features.emplace_back(kAnnouncementNotification, parameters);
else
disabled_features.push_back(kAnnouncementNotification);
scoped_feature_list_.InitWithFeaturesAndParameters(enabled_features,
disabled_features);
// Setup sign in status.
test_profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(test_profile_manager_->SetUp());
// Build the testing profile.
TestingProfile::Builder builder;
builder.SetPath(
test_profile_manager_->profiles_dir().AppendASCII(kProfileId));
builder.SetPrefService(
std::unique_ptr<sync_preferences::PrefServiceSyncable>());
builder.SetProfileName(kProfileId);
builder.SetIsNewProfile(new_profile);
if (guest_profile)
builder.SetGuestSession();
test_profile_ = builder.Build();
// Mock the sign in profile data.
DCHECK_EQ(test_profile_->GetPath(),
test_profile_manager_->profiles_dir().AppendASCII(kProfileId));
ProfileAttributesInitParams params;
params.profile_path =
test_profile_manager_->profiles_dir().AppendASCII(kProfileId);
params.profile_name = u"dummy_name";
params.gaia_id = sign_in ? GaiaId("dummy_gaia_id") : GaiaId();
params.is_consented_primary_account = sign_in;
test_profile_manager_->profile_attributes_storage()->AddProfile(
std::move(params));
// Register pref.
pref_service_ = std::make_unique<TestingPrefServiceSimple>();
AnnouncementNotificationService::RegisterProfilePrefs(
pref_service_->registry());
pref_service_->SetInteger(kCurrentVersionPrefName, current_version);
// Setup test target objects.
auto delegate = std::make_unique<NiceMock<MockDelegate>>();
delegate_ = delegate.get();
service_ = AnnouncementNotificationService::Create(
test_profile_.get(), pref_service_.get(), std::move(delegate), &clock_);
}
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfileManager> test_profile_manager_;
base::SimpleTestClock clock_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<AnnouncementNotificationService> service_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_;
std::unique_ptr<TestingProfile> test_profile_;
raw_ptr<MockDelegate> delegate_ = nullptr;
};
TEST_F(AnnouncementNotificationServiceTest, RequireSignOut) {
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"}, {kVersion, "2"}, {kRequireSignout, "true"}};
// Profile now is signed in.
Init(parameters, true, true /*sign_in*/, 1, false);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification()).Times(0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
}
TEST_F(AnnouncementNotificationServiceTest, SkipNewProfile) {
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"}, {kVersion, "2"}, {kSkipNewProfile, "true"}};
Init(parameters, true, false, 1, true /*new_profile*/);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification()).Times(0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
}
TEST_F(AnnouncementNotificationServiceTest, SkipGuestProfile) {
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"}, {kVersion, "2"}, {kSkipNewProfile, "false"}};
Init(parameters, true, false, 1, false, /*guest_profile=*/true);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification()).Times(0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
}
TEST_F(AnnouncementNotificationServiceTest, RemoteUrl) {
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"},
{kVersion, "4"},
{kSkipNewProfile, "true"},
{kAnnouncementUrl, kRemoteUrl}};
Init(parameters, true, false, 1, false);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification());
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 4);
}
// First run timestamp should be persisted even if the Finch parameter is not
// received or the feature is disabled.
TEST_F(AnnouncementNotificationServiceTest, SaveFirstRunTimeOnFirstRun) {
base::Time now = SetNow("30 May 2018 12:00:00");
std::map<std::string, std::string> parameters;
Init(parameters, false /*enable_feature*/, false, -1, true);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(true));
EXPECT_CALL(*delegate(), ShowNotification()).Times(0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), -1);
EXPECT_EQ(FirstRunTimePref(), now);
}
// First run timestamp should not be persisted when not on first run.
TEST_F(AnnouncementNotificationServiceTest, SaveFirstRunTimeNotFirstRun) {
SetNow("30 May 2018 12:00:00");
std::map<std::string, std::string> parameters;
Init(parameters, false /*enable_feature*/, false, -1, true);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), -1);
EXPECT_EQ(FirstRunTimePref(), base::Time());
}
// Not to show notification if first run timestamp happens after Finch parameter
// timestamp.
TEST_F(AnnouncementNotificationServiceTest, SkipFirstRunAfterTimeNotShow) {
SetNow("10 Feb 2020 13:00:00 GMT");
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"},
{kVersion, "2"},
{kSkipNewProfile, "false"},
{kSkipFirstRunAfterTime, "10 Feb 2020 12:15:00 GMT"}};
Init(parameters, true, false, 1, false);
auto first_run_time = SetFirstRunTimePref("10 Feb 2020 12:30:00 GMT");
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification()).Times(0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
EXPECT_EQ(FirstRunTimePref(), first_run_time);
}
// Show notification if first run timestamp happens before Finch parameter
// timestamp.
TEST_F(AnnouncementNotificationServiceTest, SkipFirstRunAfterTimeShow) {
SetNow("10 Feb 2020 13:00:00 GMT");
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"},
{kVersion, "2"},
{kSkipNewProfile, "false"},
{kSkipFirstRunAfterTime, "10 Feb 2020 12:15:00 GMT"}};
Init(parameters, true, false, 1, false);
SetFirstRunTimePref("10 Feb 2020 12:10:00 GMT");
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification());
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
}
// Show notification if there is no first run timestamp but Finch has
// "skip_first_run_after_time" parameter.
TEST_F(AnnouncementNotificationServiceTest, SkipFirstRunAfterNoFirstRunPref) {
SetNow("10 Feb 2020 13:00:00 GMT");
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"},
{kVersion, "2"},
{kSkipNewProfile, "false"},
{kSkipFirstRunAfterTime, "10 Feb 2020 12:15:00 GMT"}};
Init(parameters, true, false, 1, false);
EXPECT_EQ(FirstRunTimePref(), base::Time());
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification());
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), 2);
EXPECT_EQ(FirstRunTimePref(), base::Time());
}
// "skip_first_run_after_time" parameter should assume UTC when time zone is not
// specified.
TEST_F(AnnouncementNotificationServiceTest, SkipFirstRunAfterAssumeUTCTime) {
SetNow("10 Feb 2020 13:00:00 GMT");
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, "false"},
{kVersion, "2"},
{kSkipNewProfile, "false"},
// No time zone specified. The time string should assume UTC.
{kSkipFirstRunAfterTime, "10 Feb 2020 12:59:59"}};
Init(parameters, true, false, 1, false);
EXPECT_EQ(FirstRunTimePref(), base::Time());
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(false));
EXPECT_CALL(*delegate(), ShowNotification());
service()->MaybeShowNotification();
}
struct VersionTestParam {
bool enable_feature;
bool skip_first_run;
bool is_first_run;
int version;
int current_version;
bool show_notification_called;
int expected_version_pref;
};
class AnnouncementNotificationServiceVersionTest
: public AnnouncementNotificationServiceTest,
public ::testing::WithParamInterface<VersionTestParam> {
public:
AnnouncementNotificationServiceVersionTest() = default;
AnnouncementNotificationServiceVersionTest(
const AnnouncementNotificationServiceVersionTest&) = delete;
AnnouncementNotificationServiceVersionTest& operator=(
const AnnouncementNotificationServiceVersionTest&) = delete;
~AnnouncementNotificationServiceVersionTest() override = default;
};
const VersionTestParam kVersionTestParams[] = {
// First run. No current version in pref, has Finch parameter.
{true, false /*skip_first_run*/, true /*is_first_run*/, 1, -1, true, 1},
// Skip first run.
{true, true /*skip_first_run*/, true /*is_first_run*/, 2, -1, false, 2},
{true, true /*skip_first_run*/, false /*is_first_run*/, 2, -1, true, 2},
// DisableFeature
{false /*enable_feature*/, false, false, 1, -1, false, -1},
// Same version between Finch parameter and preference.
{true, false, false, 3 /*version*/, 3 /*current_version*/, false, 3},
// New version from Finch parameter.
{true, false, false, 4 /*version*/, 3 /*current_version*/, true, 4},
// OldVersion
{true, false, false, 2 /*version*/, 3 /*current_version*/, false, 2},
// No current version in pref, no Finch parameter.
{true, false, false, -1 /*version*/, -1 /*current_version*/, false, -1},
// Has current version in pref, no Finch parameter.
{true, false, false, -1 /*version*/, 10 /*current_version*/, false, 10},
};
TEST_P(AnnouncementNotificationServiceVersionTest, VersionTest) {
const auto& param = GetParam();
auto now = SetNow("10 Feb 2020 13:00:00");
std::map<std::string, std::string> parameters = {
{kSkipFirstRun, base::ToString(param.skip_first_run)},
{kVersion, base::NumberToString(param.version)}};
Init(parameters, param.enable_feature, false /*sign_in*/,
param.current_version, false);
ON_CALL(*delegate(), IsFirstRun()).WillByDefault(Return(param.is_first_run));
EXPECT_CALL(*delegate(), ShowNotification())
.Times(param.show_notification_called ? 1 : 0);
service()->MaybeShowNotification();
EXPECT_EQ(CurrentVersionPref(), param.expected_version_pref);
EXPECT_EQ(FirstRunTimePref(), param.is_first_run ? now : base::Time());
}
INSTANTIATE_TEST_SUITE_P(All,
AnnouncementNotificationServiceVersionTest,
testing::ValuesIn(kVersionTestParams));
} // namespace