blob: 492e750743ce2a4f44d8518657d53d121a98269c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Foundation/Foundation.h>
#import <set>
#import <vector>
#import "base/json/values_util.h"
#import "base/test/scoped_feature_list.h"
#import "base/test/simple_test_clock.h"
#import "base/values.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/feature_engagement/test/mock_tracker.h"
#import "components/prefs/pref_registry_simple.h"
#import "components/prefs/testing_pref_service.h"
#import "ios/chrome/browser/promos_manager/constants.h"
#import "ios/chrome/browser/promos_manager/features.h"
#import "ios/chrome/browser/promos_manager/impression_limit.h"
#import "ios/chrome/browser/promos_manager/promo.h"
#import "ios/chrome/browser/promos_manager/promo_config.h"
#import "ios/chrome/browser/promos_manager/promos_manager.h"
#import "ios/chrome/browser/promos_manager/promos_manager_impl.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "testing/platform_test.h"
#import "third_party/abseil-cpp/absl/types/optional.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using PromoContext = PromosManagerImpl::PromoContext;
namespace {
// The number of days since the Unix epoch; one day, in this context, runs
// from UTC midnight to UTC midnight.
int TodaysDay() {
return (base::Time::Now() - base::Time::UnixEpoch()).InDays();
}
const base::TimeDelta kTimeDelta1Day = base::Days(1);
const base::TimeDelta kTimeDelta1Hour = base::Hours(1);
const PromoContext kPromoContextForActive = PromoContext{
.was_pending = false,
};
} // namespace
class PromosManagerImplTest : public PlatformTest {
public:
PromosManagerImplTest();
~PromosManagerImplTest() override;
// Creates a mock promo without impression limits.
Promo* TestPromo();
// Creates a mock promo with impression limits.
Promo* TestPromoWithImpressionLimits();
// Creates mock impression limits.
NSArray<ImpressionLimit*>* TestImpressionLimits();
protected:
// Creates PromosManager with empty pref data.
void CreatePromosManager();
// Create pref registry for tests.
void CreatePrefs();
base::SimpleTestClock test_clock_;
std::unique_ptr<TestingPrefServiceSimple> local_state_;
std::unique_ptr<PromosManagerImpl> promos_manager_;
std::unique_ptr<feature_engagement::test::MockTracker> mock_tracker_;
base::test::ScopedFeatureList scoped_feature_list_;
};
PromosManagerImplTest::PromosManagerImplTest() {
test_clock_.SetNow(base::Time::Now());
}
PromosManagerImplTest::~PromosManagerImplTest() {}
NSArray<ImpressionLimit*>* PromosManagerImplTest::TestImpressionLimits() {
static NSArray<ImpressionLimit*>* limits;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ImpressionLimit* oncePerWeek = [[ImpressionLimit alloc] initWithLimit:1
forNumDays:7];
ImpressionLimit* twicePerMonth = [[ImpressionLimit alloc] initWithLimit:2
forNumDays:31];
limits = @[ oncePerWeek, twicePerMonth ];
});
return limits;
}
Promo* PromosManagerImplTest::TestPromo() {
return [[Promo alloc] initWithIdentifier:promos_manager::Promo::Test];
}
Promo* PromosManagerImplTest::TestPromoWithImpressionLimits() {
return [[Promo alloc] initWithIdentifier:promos_manager::Promo::Test
andImpressionLimits:TestImpressionLimits()];
}
void PromosManagerImplTest::CreatePromosManager() {
CreatePrefs();
mock_tracker_ = std::make_unique<feature_engagement::test::MockTracker>();
promos_manager_ = std::make_unique<PromosManagerImpl>(
local_state_.get(), &test_clock_, mock_tracker_.get(), nullptr);
promos_manager_->Init();
}
// Create pref registry for tests.
void PromosManagerImplTest::CreatePrefs() {
local_state_ = std::make_unique<TestingPrefServiceSimple>();
local_state_->registry()->RegisterListPref(
prefs::kIosPromosManagerImpressions);
local_state_->registry()->RegisterListPref(
prefs::kIosPromosManagerActivePromos);
local_state_->registry()->RegisterListPref(
prefs::kIosPromosManagerSingleDisplayActivePromos);
local_state_->registry()->RegisterDictionaryPref(
prefs::kIosPromosManagerSingleDisplayPendingPromos);
}
// Tests the initializer correctly creates a PromosManagerImpl* with the
// specified Pref service.
TEST_F(PromosManagerImplTest, InitWithPrefService) {
CreatePromosManager();
EXPECT_NE(local_state_->FindPreference(prefs::kIosPromosManagerImpressions),
nullptr);
EXPECT_NE(local_state_->FindPreference(prefs::kIosPromosManagerActivePromos),
nullptr);
EXPECT_NE(local_state_->FindPreference(
prefs::kIosPromosManagerSingleDisplayActivePromos),
nullptr);
EXPECT_FALSE(local_state_->HasPrefPath(prefs::kIosPromosManagerImpressions));
EXPECT_FALSE(local_state_->HasPrefPath(prefs::kIosPromosManagerActivePromos));
EXPECT_FALSE(local_state_->HasPrefPath(
prefs::kIosPromosManagerSingleDisplayActivePromos));
}
// Tests promos_manager::NameForPromo correctly returns the string
// representation of a given promo.
TEST_F(PromosManagerImplTest, ReturnsNameForTestPromo) {
EXPECT_EQ(promos_manager::NameForPromo(promos_manager::Promo::Test),
"promos_manager::Promo::Test");
}
// Tests promos_manager::PromoForName correctly returns the
// promos_manager::Promo given its string name.
TEST_F(PromosManagerImplTest, ReturnsTestPromoForName) {
EXPECT_EQ(promos_manager::PromoForName("promos_manager::Promo::Test"),
promos_manager::Promo::Test);
}
// Tests promos_manager::PromoForName correctly returns absl::nullopt for bad
// input.
TEST_F(PromosManagerImplTest, ReturnsNulloptForBadName) {
EXPECT_FALSE(promos_manager::PromoForName("promos_manager::Promo::FOOBAR")
.has_value());
}
// Tests PromosManagerImplTest::TestPromo() correctly creates one mock promo.
TEST_F(PromosManagerImplTest, CreatesPromo) {
Promo* promo = TestPromo();
EXPECT_NE(promo, nil);
EXPECT_EQ((int)promo.impressionLimits.count, 0);
}
// Tests PromosManagerImplTest::TestPromoWithImpressionLimits() correctly
// creates one mock promo with mock impression limits.
TEST_F(PromosManagerImplTest, CreatesPromoWithImpressionLimits) {
Promo* promoWithImpressionLimits = TestPromoWithImpressionLimits();
EXPECT_NE(promoWithImpressionLimits, nil);
EXPECT_EQ((int)promoWithImpressionLimits.impressionLimits.count, 2);
}
// Tests PromosManagerImplTest::TestImpressionLimits() correctly creates two
// mock impression limits.
TEST_F(PromosManagerImplTest, CreatesImpressionLimits) {
NSArray<ImpressionLimit*>* impressionLimits = TestImpressionLimits();
EXPECT_NE(impressionLimits, nil);
EXPECT_EQ(impressionLimits[0].numImpressions, 1);
EXPECT_EQ(impressionLimits[0].numDays, 7);
EXPECT_EQ(impressionLimits[1].numImpressions, 2);
EXPECT_EQ(impressionLimits[1].numDays, 31);
}
// Tests PromosManager::ImpressionCounts() correctly returns a counts list from
// an impression counts map.
TEST_F(PromosManagerImplTest, ReturnsImpressionCounts) {
std::map<promos_manager::Promo, int> promo_impression_counts = {
{promos_manager::Promo::Test, 3},
{promos_manager::Promo::AppStoreRating, 1},
{promos_manager::Promo::CredentialProviderExtension, 6},
{promos_manager::Promo::DefaultBrowser, 5},
};
std::vector<int> counts = {3, 5, 1, 6};
EXPECT_EQ(promos_manager_->ImpressionCounts(promo_impression_counts), counts);
}
// Tests PromosManager::ImpressionCounts() correctly returns an empty counts
// list for an empty impression counts map.
TEST_F(PromosManagerImplTest, ReturnsEmptyImpressionCounts) {
std::map<promos_manager::Promo, int> promo_impression_counts;
std::vector<int> counts;
EXPECT_EQ(promos_manager_->ImpressionCounts(promo_impression_counts), counts);
}
// Tests PromosManager::TotalImpressionCount() correctly adds the counts of
// different promos from an impression counts map.
TEST_F(PromosManagerImplTest, ReturnsTotalImpressionCount) {
std::map<promos_manager::Promo, int> promo_impression_counts = {
{promos_manager::Promo::Test, 3},
{promos_manager::Promo::AppStoreRating, 1},
{promos_manager::Promo::CredentialProviderExtension, 6},
{promos_manager::Promo::DefaultBrowser, 5},
};
EXPECT_EQ(promos_manager_->TotalImpressionCount(promo_impression_counts), 15);
}
// Tests PromosManager::TotalImpressionCount() returns zero for an empty
// impression counts map.
TEST_F(PromosManagerImplTest, ReturnsZeroForTotalImpressionCount) {
std::map<promos_manager::Promo, int> promo_impression_counts;
EXPECT_EQ(promos_manager_->TotalImpressionCount(promo_impression_counts), 0);
}
// Tests PromosManager::MaxImpressionCount() correctly returns the max
// impression count from an impression counts map.
TEST_F(PromosManagerImplTest, ReturnsMaxImpressionCount) {
std::map<promos_manager::Promo, int> promo_impression_counts = {
{promos_manager::Promo::Test, 3},
{promos_manager::Promo::AppStoreRating, 1},
{promos_manager::Promo::CredentialProviderExtension, 6},
{promos_manager::Promo::DefaultBrowser, 5},
};
EXPECT_EQ(promos_manager_->MaxImpressionCount(promo_impression_counts), 6);
}
// Tests PromosManager::MaxImpressionCount() correctly returns zero for an empty
// impression counts map.
TEST_F(PromosManagerImplTest, ReturnsZeroForMaxImpressionCount) {
std::map<promos_manager::Promo, int> promo_impression_counts;
EXPECT_EQ(promos_manager_->MaxImpressionCount(promo_impression_counts), 0);
}
// Tests PromosManager::AnyImpressionLimitTriggered() correctly detects an
// impression limit is triggered.
TEST_F(PromosManagerImplTest, DetectsSingleImpressionLimitTriggered) {
ImpressionLimit* thricePerWeek = [[ImpressionLimit alloc] initWithLimit:3
forNumDays:7];
NSArray<ImpressionLimit*>* limits = @[
thricePerWeek,
];
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(3, 1, limits), true);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(4, 5, limits), true);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(4, 6, limits), true);
// This is technically the 8th day, so it's the start of a new week, and
// doesn't hit the limit.
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(3, 7, limits), false);
}
// Tests PromosManager::AnyImpressionLimitTriggered() correctly detects an
// impression limit is triggered over multiple impression limits.
TEST_F(PromosManagerImplTest, DetectsOneOfMultipleImpressionLimitsTriggered) {
ImpressionLimit* onceEveryTwoDays = [[ImpressionLimit alloc] initWithLimit:1
forNumDays:2];
ImpressionLimit* thricePerWeek = [[ImpressionLimit alloc] initWithLimit:3
forNumDays:7];
NSArray<ImpressionLimit*>* limits = @[
thricePerWeek,
onceEveryTwoDays,
];
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(1, 1, limits), true);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(1, 2, limits), false);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(2, 2, limits), false);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(2, 4, limits), false);
}
// Tests PromosManager::AnyImpressionLimitTriggered() correctly detects no
// impression limits are triggered.
TEST_F(PromosManagerImplTest, DetectsNoImpressionLimitTriggered) {
ImpressionLimit* onceEveryTwoDays = [[ImpressionLimit alloc] initWithLimit:1
forNumDays:2];
ImpressionLimit* thricePerWeek = [[ImpressionLimit alloc] initWithLimit:3
forNumDays:7];
NSArray<ImpressionLimit*>* limits = @[ onceEveryTwoDays, thricePerWeek ];
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(1, 1, nil), false);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(0, 3, limits), false);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(1, 5, limits), false);
EXPECT_EQ(promos_manager_->AnyImpressionLimitTriggered(2, 5, limits), false);
}
// Tests PromosManager::CanShowPromo() correctly allows a promo to be shown
// because it hasn't met any impression limits.
TEST_F(PromosManagerImplTest, DecidesCanShowPromo) {
CreatePromosManager();
const std::vector<promos_manager::Impression> zeroImpressions = {};
EXPECT_EQ(promos_manager_->CanShowPromo(promos_manager::Promo::Test,
zeroImpressions),
true);
}
// Tests PromosManager::CanShowPromo() correctly denies promos from being shown
// as they've triggered impression limits.
TEST_F(PromosManagerImplTest, DecidesCannotShowPromo) {
CreatePromosManager();
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 14, false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today - 180,
false),
};
// False because triggers no more than 1 impression per month global
// impression limit.
EXPECT_EQ(
promos_manager_->CanShowPromo(promos_manager::Promo::Test, impressions),
false);
EXPECT_EQ(promos_manager_->CanShowPromo(promos_manager::Promo::DefaultBrowser,
impressions),
false);
EXPECT_EQ(promos_manager_->CanShowPromo(promos_manager::Promo::AppStoreRating,
impressions),
false);
// False because an impression has already been shown this month, even though
// it's not the CredentialProviderExtension promo.
EXPECT_EQ(
promos_manager_->CanShowPromo(
promos_manager::Promo::CredentialProviderExtension, impressions),
false);
}
// Tests PromosManager::SortPromos() correctly returns a list of active
// promos sorted by impression history.
TEST_F(PromosManagerImplTest, SortPromos) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, kPromoContextForActive},
{promos_manager::Promo::CredentialProviderExtension,
kPromoContextForActive},
{promos_manager::Promo::AppStoreRating, kPromoContextForActive},
{promos_manager::Promo::DefaultBrowser, kPromoContextForActive},
};
int today = TodaysDay();
promos_manager_->impression_history_ = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 14, false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today - 180,
false),
};
std::vector<promos_manager::Promo> expected = {
promos_manager::Promo::CredentialProviderExtension,
promos_manager::Promo::AppStoreRating,
promos_manager::Promo::DefaultBrowser,
promos_manager::Promo::Test,
};
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests PromosManager::SortPromos() correctly returns a list of
// promos sorted by least recently shown (with some impressions /
// belonging to inactive promo campaigns).
TEST_F(PromosManagerImplTest, SortPromosWithSomeInactivePromos) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, kPromoContextForActive},
{promos_manager::Promo::AppStoreRating, kPromoContextForActive},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 14, false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today - 180,
false),
};
const std::vector<promos_manager::Promo> expected = {
promos_manager::Promo::AppStoreRating,
promos_manager::Promo::Test,
};
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests PromosManager::SortPromos() correctly returns a list of
// promos when multiple promos are tied for least recently shown.
TEST_F(PromosManagerImplTest, ReturnsSortPromosBreakingTies) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, kPromoContextForActive},
{promos_manager::Promo::CredentialProviderExtension,
kPromoContextForActive},
{promos_manager::Promo::AppStoreRating, kPromoContextForActive},
{promos_manager::Promo::DefaultBrowser, kPromoContextForActive},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser, today,
false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating, today,
false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today, false),
};
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos).size(), (size_t)4);
EXPECT_EQ(promos_manager_->SortPromos(active_promos)[0],
promos_manager::Promo::CredentialProviderExtension);
}
// Tests `SortPromos` returns a single promo in a list when the impression
// history contains only one active promo.
TEST_F(PromosManagerImplTest, ReturnsSortPromosWithOnlyOnePromoActive) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, kPromoContextForActive},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 14, false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today - 180,
false),
};
const std::vector<promos_manager::Promo> expected = {
promos_manager::Promo::Test,
};
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests `SortPromos` returns an empty array when there are no active promo.
TEST_F(PromosManagerImplTest,
ReturnsEmptyListWhenSortPromosHasNoActivePromoCampaigns) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos;
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 14, false),
promos_manager::Impression(
promos_manager::Promo::CredentialProviderExtension, today - 180,
false),
};
const std::vector<promos_manager::Promo> expected;
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests `SortPromos` returns an empty array when no impression history and no
// active promos exist.
TEST_F(PromosManagerImplTest,
ReturnsEmptyListWhenSortPromosHasNoImpressionHistory) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos;
const std::vector<promos_manager::Impression> impressions;
const std::vector<promos_manager::Promo> expected;
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests `SortPromos` sorts unshown promos before shown promos.
TEST_F(PromosManagerImplTest,
SortsUnshownPromosBeforeShownPromosForSortPromos) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, kPromoContextForActive},
{promos_manager::Promo::AppStoreRating, kPromoContextForActive},
{promos_manager::Promo::DefaultBrowser, kPromoContextForActive},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
};
const std::vector<promos_manager::Promo> expected = {
promos_manager::Promo::AppStoreRating,
promos_manager::Promo::DefaultBrowser,
promos_manager::Promo::Test,
};
promos_manager_->impression_history_ = impressions;
EXPECT_EQ(promos_manager_->SortPromos(active_promos), expected);
}
// Tests `SortPromos` sorts `Choice` promos before others and
// `PostRestoreSignIn` next.
TEST_F(PromosManagerImplTest, SortsPromosPreferCertainTypes) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, PromoContext{false}},
{promos_manager::Promo::DefaultBrowser, PromoContext{true}},
{promos_manager::Promo::PostRestoreSignInFullscreen, PromoContext{false}},
{promos_manager::Promo::PostRestoreSignInAlert, PromoContext{false}},
{promos_manager::Promo::Choice, PromoContext{false}},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(
promos_manager::Promo::PostRestoreSignInFullscreen, today - 1, false),
promos_manager::Impression(promos_manager::Promo::PostRestoreSignInAlert,
today - 2, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
promos_manager::Impression(promos_manager::Promo::Test, today - 8, false),
};
promos_manager_->impression_history_ = impressions;
std::vector<promos_manager::Promo> sorted =
promos_manager_->SortPromos(active_promos);
EXPECT_EQ(sorted.size(), (size_t)5);
// Choice comes first
EXPECT_TRUE(sorted[0] == promos_manager::Promo::Choice);
// tied for the type.
EXPECT_TRUE(sorted[1] == promos_manager::Promo::PostRestoreSignInFullscreen ||
sorted[1] == promos_manager::Promo::PostRestoreSignInAlert);
// with pending state, before the less recently shown promo (Test).
EXPECT_EQ(sorted[3], promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(sorted[4], promos_manager::Promo::Test);
}
// Tests `SortPromos` sorts promos with pending state before others without.
TEST_F(PromosManagerImplTest, SortsPromosPreferPendingToNonPending) {
CreatePromosManager();
const std::map<promos_manager::Promo, PromoContext> active_promos = {
{promos_manager::Promo::Test, PromoContext{true}},
{promos_manager::Promo::DefaultBrowser, PromoContext{false}},
{promos_manager::Promo::AppStoreRating, PromoContext{true}},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today - 1, false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 2, false),
promos_manager::Impression(promos_manager::Promo::DefaultBrowser,
today - 7, false),
};
promos_manager_->impression_history_ = impressions;
std::vector<promos_manager::Promo> sorted =
promos_manager_->SortPromos(active_promos);
EXPECT_EQ(sorted.size(), (size_t)3);
// The first 2 are tied with the pending state.
EXPECT_TRUE(sorted[0] == promos_manager::Promo::Test ||
sorted[0] == promos_manager::Promo::AppStoreRating);
// The one without pending state is at the end.
EXPECT_EQ(sorted[2], promos_manager::Promo::DefaultBrowser);
}
// Tests PromosManager::ImpressionHistory() correctly ingests impression history
// (base::Value::List) and returns corresponding
// std::vector<promos_manager::Impression>.
TEST_F(PromosManagerImplTest, ReturnsImpressionHistory) {
int today = TodaysDay();
base::Value::Dict first_impression;
first_impression.Set(
promos_manager::kImpressionPromoKey,
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
first_impression.Set(promos_manager::kImpressionDayKey, today);
base::Value::Dict second_impression;
second_impression.Set(
promos_manager::kImpressionPromoKey,
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
second_impression.Set(promos_manager::kImpressionDayKey, today - 7);
base::Value::List impressions;
impressions.Append(first_impression.Clone());
impressions.Append(second_impression.Clone());
std::vector<promos_manager::Impression> expected = {
promos_manager::Impression(promos_manager::Promo::DefaultBrowser, today,
false),
promos_manager::Impression(promos_manager::Promo::AppStoreRating,
today - 7, false),
};
std::vector<promos_manager::Impression> result =
promos_manager_->ImpressionHistory(impressions);
EXPECT_EQ(expected.size(), result.size());
EXPECT_EQ(expected[0].promo, result[0].promo);
EXPECT_EQ(expected[0].day, result[0].day);
EXPECT_EQ(expected[1].promo, result[1].promo);
EXPECT_EQ(expected[1].day, result[1].day);
}
// Tests PromosManager::ImpressionHistory() correctly ingests empty impression
// history (base::Value::List) and returns empty
// std::vector<promos_manager::Impression>.
TEST_F(PromosManagerImplTest, ReturnsBlankImpressionHistoryForBlankPrefs) {
base::Value::List impressions;
std::vector<promos_manager::Impression> result =
promos_manager_->ImpressionHistory(impressions);
EXPECT_TRUE(result.empty());
}
// Tests PromosManager::ImpressionHistory() correctly ingests impression history
// with malformed data (base::Value::List) and returns corresponding
// std::vector<promos_manager::Impression> without malformed entries.
TEST_F(PromosManagerImplTest,
ReturnsImpressionHistoryBySkippingMalformedEntries) {
int today = TodaysDay();
base::Value::Dict first_impression;
first_impression.Set(
promos_manager::kImpressionPromoKey,
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
first_impression.Set(promos_manager::kImpressionDayKey, today);
base::Value::Dict second_impression;
second_impression.Set("foobar", promos_manager::NameForPromo(
promos_manager::Promo::AppStoreRating));
second_impression.Set(promos_manager::kImpressionDayKey, today - 7);
base::Value::List impressions;
impressions.Append(first_impression.Clone());
impressions.Append(second_impression.Clone());
std::vector<promos_manager::Impression> expected = {
promos_manager::Impression(promos_manager::Promo::DefaultBrowser, today,
false),
};
std::vector<promos_manager::Impression> result =
promos_manager_->ImpressionHistory(impressions);
EXPECT_EQ(expected.size(), result.size());
EXPECT_EQ(expected[0].promo, result[0].promo);
EXPECT_EQ(expected[0].day, result[0].day);
}
// Tests PromosManager::ActivePromos() correctly ingests active promos
// (base::Value::List) and returns corresponding
// std::vector<promos_manager::Promo>.
TEST_F(PromosManagerImplTest, ReturnsActivePromos) {
base::Value::List promos;
promos.Append("promos_manager::Promo::DefaultBrowser");
promos.Append("promos_manager::Promo::AppStoreRating");
promos.Append("promos_manager::Promo::CredentialProviderExtension");
std::set<promos_manager::Promo> expected = {
promos_manager::Promo::DefaultBrowser,
promos_manager::Promo::AppStoreRating,
promos_manager::Promo::CredentialProviderExtension,
};
std::set<promos_manager::Promo> result =
promos_manager_->ActivePromos(promos);
EXPECT_EQ(expected, promos_manager_->ActivePromos(promos));
}
// Tests PromosManager::ActivePromos() correctly ingests empty active promos
// (base::Value::List) and returns empty std::set<promos_manager::Promo>.
TEST_F(PromosManagerImplTest, ReturnsBlankActivePromosForBlankPrefs) {
base::Value::List promos;
std::set<promos_manager::Promo> result =
promos_manager_->ActivePromos(promos);
EXPECT_TRUE(result.empty());
}
// Tests PromosManager::ActivePromos() correctly ingests active promos with
// malformed data (base::Value::List) and returns corresponding
// std::vector<promos_manager::Promo> with malformed entries pruned.
TEST_F(PromosManagerImplTest, ReturnsActivePromosAndSkipsMalformedData) {
base::Value::List promos;
promos.Append("promos_manager::Promo::DefaultBrowser");
promos.Append("promos_manager::Promo::AppStoreRating");
promos.Append("promos_manager::Promo::FOOBAR");
std::set<promos_manager::Promo> expected = {
promos_manager::Promo::DefaultBrowser,
promos_manager::Promo::AppStoreRating,
};
std::set<promos_manager::Promo> result =
promos_manager_->ActivePromos(promos);
EXPECT_EQ(expected, promos_manager_->ActivePromos(promos));
}
// Tests `InitializePendingPromos` initializes with pending promos.
TEST_F(PromosManagerImplTest, InitializePendingPromos) {
CreatePromosManager();
// write to Pref
base::Value::Dict promos;
promos.Set("promos_manager::Promo::DefaultBrowser",
base::TimeToValue(test_clock_.Now()));
promos.Set("promos_manager::Promo::AppStoreRating",
base::TimeToValue(test_clock_.Now()));
local_state_->SetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos,
std::move(promos));
std::map<promos_manager::Promo, base::Time> expected;
expected[promos_manager::Promo::DefaultBrowser] = test_clock_.Now();
expected[promos_manager::Promo::AppStoreRating] = test_clock_.Now();
promos_manager_->InitializePendingPromos();
EXPECT_EQ(expected, promos_manager_->single_display_pending_promos_);
}
// Tests `InitializePendingPromos` initializes empty Pref.
TEST_F(PromosManagerImplTest, InitializePendingPromosEmpty) {
CreatePromosManager();
promos_manager_->InitializePendingPromos();
std::map<promos_manager::Promo, base::Time> expected;
EXPECT_EQ(expected, promos_manager_->single_display_pending_promos_);
}
// Tests `InitializePendingPromos` initializes with malformed data with
// malformed entries pruned.
TEST_F(PromosManagerImplTest, InitializePendingPromosMalformedData) {
CreatePromosManager();
// write to Pref
base::Value::Dict promos;
promos.Set("promos_manager::Promo::DefaultBrowser",
base::TimeToValue(test_clock_.Now()));
promos.Set("promos_manager::Promo::Foo",
base::TimeToValue(test_clock_.Now()));
promos.Set("promos_manager::Promo::AppStoreRating", base::Value(1));
local_state_->SetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos,
std::move(promos));
std::map<promos_manager::Promo, base::Time> expected;
expected[promos_manager::Promo::DefaultBrowser] = test_clock_.Now();
promos_manager_->InitializePendingPromos();
EXPECT_EQ(expected, promos_manager_->single_display_pending_promos_);
}
// Tests PromosManager::RegisterPromoForContinuousDisplay() correctly registers
// a promo for continuous display by writing the promo's name to the pref
// `kIosPromosManagerActivePromos`.
TEST_F(PromosManagerImplTest, RegistersPromoForContinuousDisplay) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerActivePromos).empty());
// Initial active promos state.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)2);
// Register new promo.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)3);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos)[0],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerActivePromos)[1],
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerActivePromos)[2],
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
}
// Tests PromosManager::RegisterPromoForContinuousDisplay() correctly registers
// a promo for continuous display by writing the promo's name to the pref
// `kIosPromosManagerActivePromos` and immediately updates active_promos_ to
// reflect the new state.
TEST_F(PromosManagerImplTest,
RegistersPromoForContinuousDisplayAndImmediatelyUpdateVariables) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerActivePromos).empty());
// Initial active promos state.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)2);
// Register new promo.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)3);
}
// Tests PromosManager::RegisterPromoForContinuousDisplay() correctly registers
// a promo—for the very first time—for continuous display by writing the
// promo's name to the pref `kIosPromosManagerActivePromos`.
TEST_F(PromosManagerImplTest,
RegistersPromoForContinuousDisplayForEmptyActivePromos) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerActivePromos).empty());
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)1);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerActivePromos)[0],
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
}
// Tests PromosManager::RegisterPromoForContinuousDisplay() correctly registers
// an already-registered promo for continuous display by first erasing, and then
// re-writing, the promo's name to the pref `kIosPromosManagerActivePromos`;
// tests no duplicate entries are created.
TEST_F(PromosManagerImplTest,
RegistersAlreadyRegisteredPromoForContinuousDisplay) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerActivePromos).empty());
// Initial active promos state.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)2);
// Register existing promo.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)2);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerActivePromos)[0],
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos)[1],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
}
// Tests PromosManager::RegisterPromoForContinuousDisplay() correctly registers
// an already-registered promo for continuous display—for the very first time—by
// first erasing, and then re-writing, the promo's name to the pref
// `kIosPromosManagerActivePromos`; tests no duplicate entries are created.
TEST_F(
PromosManagerImplTest,
RegistersAlreadyRegisteredPromoForContinuousDisplayForEmptyActivePromos) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerActivePromos).empty());
// Initial active promos state.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
// Register existing promo.
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)1);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos)[0],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
}
// Tests PromosManager::RegisterPromoForSingleDisplay() correctly registers
// a promo for single display by writing the promo's name to the pref
// `kIosPromosManagerSingleDisplayActivePromos`.
TEST_F(PromosManagerImplTest, RegistersPromoForSingleDisplay) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)2);
// Register new promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)3);
EXPECT_EQ(local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[0],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
EXPECT_EQ(
local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[1],
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
EXPECT_EQ(
local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[2],
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
}
// Tests `RegisterPromoForSingleDisplay` with `becomes_active_after_period`
// registers a promo for single display by writing the promo's name and the
// calculated time to the pref `kIosPromosManagerSingleDisplayPendingPromos`.
TEST_F(PromosManagerImplTest,
RegistersPromoForSingleDisplayWithBecomesActivePeriod) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.empty());
// Initial state: 1 active promo, 0 pending promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)0);
// Register new promo with becomes_active_period.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
// End state: 1 active promo, 1 pending promo.
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)1);
// Active promo in Pref doesn't change.
EXPECT_EQ(
local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[0],
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
// Pending promo in Pref is updated with the correct time.
absl::optional<base::Time> actual_becomes_active_time = ValueToTime(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.Find(promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension)));
EXPECT_TRUE(actual_becomes_active_time.has_value());
EXPECT_EQ(actual_becomes_active_time.value(),
test_clock_.Now() + kTimeDelta1Day);
}
// Tests PromosManager::RegisterPromoForSingleDisplay() correctly registers
// a promo for single display by writing the promo's name to the pref
// `kIosPromosManagerSingleDisplayActivePromos` and immediately updates
// single_display_active_promos_ to reflect the new state.
TEST_F(PromosManagerImplTest,
RegistersPromoForSingleDisplayAndImmediatelyUpdateVariables) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)2);
// Register new promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)3);
}
// Tests `RegisterPromoForSingleDisplay` with `becomes_active_after_period`
// registers a promo for single display by writing the promo's name and the
// calculated time to the pref `kIosPromosManagerSingleDisplayActivePromos`
// and updates single_display_pending_promos_.
TEST_F(
PromosManagerImplTest,
RegistersPromoForSingleDisplayWithBecomesActivePeriodAndUpdateVariables) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.empty());
// Register
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
// End state: 0 active promo, 1 pending promo
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)0);
EXPECT_EQ(promos_manager_->single_display_pending_promos_.size(), (size_t)1);
base::Time actual_becomes_active_time =
promos_manager_->single_display_pending_promos_
[promos_manager::Promo::CredentialProviderExtension];
EXPECT_EQ(actual_becomes_active_time, (test_clock_.Now() + kTimeDelta1Day));
}
// Tests PromosManager::RegisterPromoForSingleDisplay() correctly registers
// a promo for single display—for the very first time—by writing the promo's
// name to the pref `kIosPromosManagerSingleDisplayActivePromos`.
TEST_F(PromosManagerImplTest,
RegistersPromoForSingleDisplayForEmptyActivePromos) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
EXPECT_EQ(
local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[0],
promos_manager::NameForPromo(promos_manager::Promo::DefaultBrowser));
}
// Tests PromosManager::RegisterPromoForSingleDisplay() correctly registers
// an already-registered promo for single display by first erasing, and then
// re-writing, the promo's name to the pref
// `kIosPromosManagerSingleDisplayActivePromos`; tests no duplicate entries are
// created.
TEST_F(PromosManagerImplTest, RegistersAlreadyRegisteredPromoForSingleDisplay) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::AppStoreRating);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)2);
// Register existing promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)2);
EXPECT_EQ(
local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[0],
promos_manager::NameForPromo(promos_manager::Promo::AppStoreRating));
EXPECT_EQ(local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[1],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
}
// Tests PromosManager::RegisterPromoForSingleDisplay() correctly registers
// an already-registered promo for single display—for the very first time—by
// first erasing, and then re-writing, the promo's name to the pref
// `kIosPromosManagerSingleDisplayActivePromos`; tests no duplicate entries are
// created.
TEST_F(PromosManagerImplTest,
RegistersAlreadyRegisteredPromoForSingleDisplayForEmptyActivePromos) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
// Register existing promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
EXPECT_EQ(local_state_->GetList(
prefs::kIosPromosManagerSingleDisplayActivePromos)[0],
promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension));
}
// Tests `RegisterPromoForSingleDisplay` with `becomes_active_after_period`
// correctly registers an already-registered promo for single display by
// overriding the existing entry in the pref
// `kIosPromosManagerSingleDisplayPendingPromos`.
TEST_F(PromosManagerImplTest,
RegistersAlreadyRegisteredPromoForSingleDisplayWithBecomesActiveTime) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.empty());
// Initial pending promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
// Register the same promo.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day * 2);
// End state: only the second registered promo is in the pref.
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)1);
absl::optional<base::Time> actual_becomes_active_time = ValueToTime(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.Find(promos_manager::NameForPromo(
promos_manager::Promo::CredentialProviderExtension)));
EXPECT_TRUE(actual_becomes_active_time.has_value());
EXPECT_EQ(actual_becomes_active_time.value(),
test_clock_.Now() + kTimeDelta1Day * 2);
}
// Tests PromosManager::InitializePromoImpressionLimits() correctly registers
// promo-specific impression limits.
TEST_F(PromosManagerImplTest, RegistersPromoSpecificImpressionLimits) {
CreatePromosManager();
ImpressionLimit* onceEveryTwoDays = [[ImpressionLimit alloc] initWithLimit:1
forNumDays:2];
ImpressionLimit* thricePerWeek = [[ImpressionLimit alloc] initWithLimit:3
forNumDays:7];
NSArray<ImpressionLimit*>* defaultBrowserLimits = @[
onceEveryTwoDays,
];
NSArray<ImpressionLimit*>* credentialProviderLimits = @[
thricePerWeek,
];
PromoConfigsSet promoImpressionLimits;
promoImpressionLimits.emplace(promos_manager::Promo::DefaultBrowser, nullptr,
defaultBrowserLimits);
promoImpressionLimits.emplace(
promos_manager::Promo::CredentialProviderExtension, nullptr,
credentialProviderLimits);
promos_manager_->InitializePromoConfigs(std::move(promoImpressionLimits));
EXPECT_EQ(promos_manager_->PromoImpressionLimits(
promos_manager::Promo::DefaultBrowser),
defaultBrowserLimits);
EXPECT_EQ(promos_manager_->PromoImpressionLimits(
promos_manager::Promo::CredentialProviderExtension),
credentialProviderLimits);
}
// Tests PromosManager::RecordImpression() correctly records a new impression.
TEST_F(PromosManagerImplTest, RecordsImpression) {
CreatePromosManager();
promos_manager_->RecordImpression(promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerImpressions).size(),
(size_t)1);
promos_manager_->RecordImpression(
promos_manager::Promo::CredentialProviderExtension);
const auto& impression_history =
local_state_->GetList(prefs::kIosPromosManagerImpressions);
const base::Value::Dict& first_impression = impression_history[0].GetDict();
const base::Value::Dict& second_impression = impression_history[1].GetDict();
EXPECT_EQ(impression_history.size(), (size_t)2);
EXPECT_EQ(*first_impression.FindString(promos_manager::kImpressionPromoKey),
"promos_manager::Promo::DefaultBrowser");
EXPECT_TRUE(
first_impression.FindInt(promos_manager::kImpressionDayKey).has_value());
EXPECT_EQ(first_impression.FindInt(promos_manager::kImpressionDayKey).value(),
TodaysDay());
EXPECT_EQ(*second_impression.FindString(promos_manager::kImpressionPromoKey),
"promos_manager::Promo::CredentialProviderExtension");
EXPECT_TRUE(
second_impression.FindInt(promos_manager::kImpressionDayKey).has_value());
EXPECT_EQ(
second_impression.FindInt(promos_manager::kImpressionDayKey).value(),
TodaysDay());
}
// Tests PromosManager::RecordImpression() correctly records a new impression
// and immediately updates impression_history_ to reflect the new state
TEST_F(PromosManagerImplTest, RecordsImpressionAndImmediatelyUpdateVariables) {
CreatePromosManager();
promos_manager_->RecordImpression(promos_manager::Promo::DefaultBrowser);
EXPECT_EQ(promos_manager_->impression_history_.size(), (size_t)1);
promos_manager_->RecordImpression(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(promos_manager_->impression_history_.size(), (size_t)2);
}
// Tests PromosManager::DeregisterPromo() deregisters a promo, all the entries
// with the same promo type in the Pref/in-memory variables will be removed,
// regardless of the context of being single/continuous or active/pending.
TEST_F(PromosManagerImplTest, DeregistersActivePromo) {
CreatePromosManager();
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)0);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)1);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)1);
promos_manager_->DeregisterPromo(
promos_manager::Promo::CredentialProviderExtension);
// all entries with the same type are removed.
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)0);
}
// Tests PromosManager::DeregisterPromo() correctly deregisters a currently
// active promo campaign and immediately updates active_promos_ &
// single_display_active_promos_ to reflect the new state.
TEST_F(PromosManagerImplTest,
DeregistersActivePromoAndImmediatelyUpdateVariables) {
CreatePromosManager();
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)0);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)1);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)0);
EXPECT_EQ(promos_manager_->single_display_pending_promos_.size(), (size_t)0);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)1);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)1);
EXPECT_EQ(promos_manager_->single_display_pending_promos_.size(), (size_t)0);
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)1);
EXPECT_EQ(promos_manager_->single_display_pending_promos_.size(), (size_t)1);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)1);
promos_manager_->DeregisterPromo(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(promos_manager_->active_promos_.size(), (size_t)0);
EXPECT_EQ(promos_manager_->single_display_active_promos_.size(), (size_t)0);
EXPECT_EQ(promos_manager_->single_display_pending_promos_.size(), (size_t)0);
}
// Tests PromosManager::DeregisterPromo() handles the situation where the promo
// doesn't exist in a given active promos list by removing from all lists.
TEST_F(PromosManagerImplTest, DeregistersNonExistentPromo) {
CreatePromosManager();
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
promos_manager_->RegisterPromoForContinuousDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)1);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
promos_manager_->DeregisterPromo(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(local_state_->GetList(prefs::kIosPromosManagerActivePromos).size(),
(size_t)0);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)0);
}
// Tests a given single-display promo is automatically deregistered after its
// impression is recorded.
TEST_F(PromosManagerImplTest,
DeregistersSingleDisplayPromoAfterRecordedImpression) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_EQ(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.size(),
(size_t)1);
promos_manager_->RecordImpression(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_TRUE(
local_state_->GetList(prefs::kIosPromosManagerSingleDisplayActivePromos)
.empty());
}
// Tests a given single-display pending promo is automatically deregistered
// after its impression is recorded.
TEST_F(PromosManagerImplTest,
DeregistersSingleDisplayPendingPromoAfterRecordedImpression) {
CreatePromosManager();
EXPECT_TRUE(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.empty());
// Initial active promos state.
promos_manager_->RegisterPromoForSingleDisplay(
promos_manager::Promo::CredentialProviderExtension, kTimeDelta1Day);
EXPECT_EQ(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.size(),
(size_t)1);
promos_manager_->RecordImpression(
promos_manager::Promo::CredentialProviderExtension);
EXPECT_TRUE(
local_state_->GetDict(prefs::kIosPromosManagerSingleDisplayPendingPromos)
.empty());
}
// Tests `NextPromoForDisplay` returns a pending promo that has become active
// and takes precedence over other active promos.
TEST_F(PromosManagerImplTest, NextPromoForDisplayReturnsPendingPromo) {
CreatePromosManager();
promos_manager_->single_display_active_promos_ = {
promos_manager::Promo::Test,
};
promos_manager_->single_display_pending_promos_ = {
{promos_manager::Promo::CredentialProviderExtension,
test_clock_.Now() + kTimeDelta1Day},
{promos_manager::Promo::AppStoreRating,
test_clock_.Now() + kTimeDelta1Day * 2},
};
// Advance to so that the CredentialProviderExtension becomes active.
test_clock_.Advance(kTimeDelta1Day + kTimeDelta1Hour);
absl::optional<promos_manager::Promo> promo =
promos_manager_->NextPromoForDisplay();
EXPECT_TRUE(promo.has_value());
EXPECT_EQ(promo.value(), promos_manager::Promo::CredentialProviderExtension);
}
// Tests `NextPromoForDisplay` returns an active promo whose type has the
// highest priority can take precedence over other pending-becomes-active
// promos.
TEST_F(PromosManagerImplTest,
NextPromoForDisplayReturnsActivePromoOfPrioritizedType) {
CreatePromosManager();
promos_manager_->single_display_active_promos_ = {
promos_manager::Promo::PostRestoreSignInFullscreen,
};
promos_manager_->single_display_pending_promos_ = {
{promos_manager::Promo::CredentialProviderExtension,
test_clock_.Now() + kTimeDelta1Day},
};
// Advance to so that the CredentialProviderExtension becomes active.
test_clock_.Advance(kTimeDelta1Day + kTimeDelta1Hour);
absl::optional<promos_manager::Promo> promo =
promos_manager_->NextPromoForDisplay();
EXPECT_TRUE(promo.has_value());
EXPECT_EQ(promo.value(), promos_manager::Promo::PostRestoreSignInFullscreen);
}
// Tests `NextPromoForDisplay` returns empty when non of the pending promos can
// become active.
TEST_F(PromosManagerImplTest, NextPromoForDisplayReturnsEmpty) {
CreatePromosManager();
promos_manager_->single_display_active_promos_ = {};
promos_manager_->single_display_pending_promos_ = {
{promos_manager::Promo::Test, test_clock_.Now() + kTimeDelta1Hour * 2},
{promos_manager::Promo::CredentialProviderExtension,
test_clock_.Now() + kTimeDelta1Day},
};
// Advance to so that the none of the pending promo can become active.
test_clock_.Advance(kTimeDelta1Hour);
absl::optional<promos_manager::Promo> promo =
promos_manager_->NextPromoForDisplay();
EXPECT_FALSE(promo.has_value());
}
// Tests `NextPromoForDisplay` returns empty when non of the promos can pass the
// onceEveryTwoDays impression limit check.
TEST_F(PromosManagerImplTest,
NextPromoForDisplayReturnsEmptyAfterImpressionCheck) {
CreatePromosManager();
promos_manager_->single_display_active_promos_ = {
promos_manager::Promo::AppStoreRating};
promos_manager_->single_display_pending_promos_ = {
{promos_manager::Promo::Test, test_clock_.Now() + kTimeDelta1Hour},
{promos_manager::Promo::CredentialProviderExtension,
test_clock_.Now() + kTimeDelta1Hour},
};
int today = TodaysDay();
const std::vector<promos_manager::Impression> impressions = {
promos_manager::Impression(promos_manager::Promo::Test, today, false),
};
promos_manager_->impression_history_ = impressions;
// Advance the time so that all pending promos can become active, but will
// fall into the two-day window since the last impression.
test_clock_.Advance(kTimeDelta1Day);
absl::optional<promos_manager::Promo> promo =
promos_manager_->NextPromoForDisplay();
EXPECT_FALSE(promo.has_value());
}