blob: 955b0ebc625ef8de9eda0e957bfa2a400728bd8d [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/cart/cart_service.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/cart/cart_db_content.pb.h"
#include "chrome/browser/cart/cart_service_factory.h"
#include "chrome/browser/cart/fetch_discount_worker.h"
#include "chrome/browser/commerce/commerce_feature_list.h"
#include "chrome/browser/commerce/coupons/coupon_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/persisted_state_db/profile_proto_db.h"
#include "chrome/browser/persisted_state_db/profile_proto_db_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search/ntp_features.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace {
cart_db::ChromeCartContentProto BuildProto(const char* domain,
const char* merchant_url) {
cart_db::ChromeCartContentProto proto;
proto.set_key(domain);
proto.set_merchant_cart_url(merchant_url);
proto.set_timestamp(base::Time::Now().ToDoubleT());
return proto;
}
cart_db::ChromeCartContentProto BuildProtoWithProducts(
const char* domain,
const char* cart_url,
const std::vector<const char*>& product_urls) {
cart_db::ChromeCartContentProto proto;
proto.set_key(domain);
proto.set_merchant_cart_url(cart_url);
proto.set_timestamp(base::Time::Now().ToDoubleT());
for (const auto* const v : product_urls) {
proto.add_product_image_urls(v);
}
return proto;
}
cart_db::ChromeCartProductProto BuildProductProto(
const std::string& product_id) {
cart_db::ChromeCartProductProto proto;
proto.set_product_id(product_id);
return proto;
}
cart_db::ChromeCartContentProto AddDiscountToProto(
cart_db::ChromeCartContentProto proto,
const double timestamp,
const std::string& rule_id,
const int percent_off,
const char* offer_id) {
proto.mutable_discount_info()->set_last_fetched_timestamp(timestamp);
cart_db::RuleDiscountInfoProto* added_discount =
proto.mutable_discount_info()->add_rule_discount_info();
added_discount->set_rule_id(rule_id);
added_discount->set_percent_off(percent_off);
added_discount->set_raw_merchant_offer_id(offer_id);
return proto;
}
MATCHER_P(EqualsProto, message, "") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
constexpr char kFakeDataPrefix[] = "Fake:";
const char kMockMerchantA[] = "foo.com";
const char kMockMerchantURLA[] = "https://www.foo.com";
const char kMockMerchantB[] = "bar.com";
const char kMockMerchantURLB[] = "https://www.bar.com";
const char kMockMerchantC[] = "baz.com";
const char kMockMerchantURLC[] = "https://www.baz.com";
const char kProductURL[] = "https://www.product.com";
const cart_db::ChromeCartContentProto kMockProtoA =
BuildProto(kMockMerchantA, kMockMerchantURLA);
const cart_db::ChromeCartContentProto kMockProtoB =
BuildProto(kMockMerchantB, kMockMerchantURLB);
const cart_db::ChromeCartContentProto kMockProtoC =
BuildProto(kMockMerchantC, kMockMerchantURLC);
const cart_db::ChromeCartContentProto kMockProtoCWithProduct =
BuildProtoWithProducts(kMockMerchantC, kMockMerchantURLC, {kProductURL});
using ShoppingCarts =
std::vector<ProfileProtoDB<cart_db::ChromeCartContentProto>::KeyAndValue>;
using ProductInfos = std::vector<cart_db::ChromeCartProductProto>;
const ShoppingCarts kExpectedA = {{kMockMerchantA, kMockProtoA}};
const ShoppingCarts kExpectedB = {{kMockMerchantB, kMockProtoB}};
const ShoppingCarts kExpectedAB = {
{kMockMerchantB, kMockProtoB},
{kMockMerchantA, kMockProtoA},
};
const ShoppingCarts kExpectedC = {{kMockMerchantC, kMockProtoC}};
const ShoppingCarts kExpectedCWithProduct = {
{kMockMerchantC, kMockProtoCWithProduct}};
const ShoppingCarts kEmptyExpected = {};
const cart_db::ChromeCartProductProto kMockProductA =
BuildProductProto("id_foo");
const cart_db::ChromeCartProductProto kMockProductB =
BuildProductProto("id_bar");
// Value used for discount.
const char kMockMerchantADiscountRuleId[] = "1";
const int kMockMerchantADiscountsPercentOff = 10;
const char kMockMerchantADiscountsRawMerchantOfferId[] = "merchantAOfferId";
const bool kNotATester = false;
const bool kATester = true;
std::vector<cart_db::RuleDiscountInfoProto> BuildMerchantADiscountInfoProtos() {
cart_db::RuleDiscountInfoProto proto;
proto.set_rule_id(kMockMerchantADiscountRuleId);
proto.set_percent_off(kMockMerchantADiscountsPercentOff);
proto.set_raw_merchant_offer_id(kMockMerchantADiscountsRawMerchantOfferId);
return std::vector<cart_db::RuleDiscountInfoProto>(1, proto);
}
const std::vector<cart_db::RuleDiscountInfoProto> kMockMerchantADiscounts =
BuildMerchantADiscountInfoProtos();
} // namespace
class CartServiceTest : public testing::Test {
public:
CartServiceTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
testing::Test::SetUp();
TestingProfile::Builder profile_builder;
profile_builder.AddTestingFactory(
HistoryServiceFactory::GetInstance(),
HistoryServiceFactory::GetDefaultFactory());
profile_ = profile_builder.Build();
service_ = CartServiceFactory::GetForProfile(profile_.get());
}
void OperationEvaluation(base::OnceClosure closure,
bool expected_success,
bool actual_success) {
GetEvaluationBoolResult(std::move(closure), expected_success,
actual_success);
}
void GetEvaluationBoolResult(base::OnceClosure closure,
bool expected,
bool actual) {
EXPECT_EQ(expected, actual);
std::move(closure).Run();
}
void GetEvaluationURL(base::OnceClosure closure,
ShoppingCarts expected,
bool result,
ShoppingCarts found) {
EXPECT_EQ(found.size(), expected.size());
for (size_t i = 0; i < expected.size(); i++) {
EXPECT_EQ(found[i].first, expected[i].first);
EXPECT_EQ(found[i].second.merchant_cart_url(),
expected[i].second.merchant_cart_url());
for (int j = 0; j < expected[i].second.product_image_urls().size(); j++) {
EXPECT_EQ(expected[i].second.product_image_urls()[j],
found[i].second.product_image_urls()[j]);
}
}
std::move(closure).Run();
}
std::string GetCartURL(const std::string& domain) {
base::RunLoop run_loop;
std::string cart_url;
service_->LoadCart(
domain, base::BindOnce(
[](base::OnceClosure closure, std::string* out, bool result,
ShoppingCarts found) {
EXPECT_TRUE(result);
EXPECT_EQ(found.size(), 1U);
*out = found[0].second.merchant_cart_url();
std::move(closure).Run();
},
run_loop.QuitClosure(), &cart_url));
run_loop.Run();
return cart_url;
}
void GetEvaluationFakeDataDB(base::OnceClosure closure,
bool result,
ShoppingCarts found) {
EXPECT_EQ(found.size(), 6U);
for (CartDB::KeyAndValue proto_pair : found) {
EXPECT_EQ(proto_pair.second.key().rfind(kFakeDataPrefix, 0), 0U);
}
std::move(closure).Run();
}
void GetEvaluationCartHiddenStatus(base::OnceClosure closure,
bool isHidden,
bool result,
ShoppingCarts found) {
EXPECT_EQ(1U, found.size());
EXPECT_EQ(isHidden, found[0].second.is_hidden());
std::move(closure).Run();
}
void GetEvaluationCartRemovedStatus(base::OnceClosure closure,
bool isRemoved,
bool result,
ShoppingCarts found) {
EXPECT_EQ(1U, found.size());
EXPECT_EQ(isRemoved, found[0].second.is_removed());
std::move(closure).Run();
}
void GetEvaluationCartTimeStamp(base::OnceClosure closure,
double expect_timestamp,
bool result,
ShoppingCarts found) {
EXPECT_EQ(1U, found.size());
EXPECT_EQ(expect_timestamp, found[0].second.timestamp());
std::move(closure).Run();
}
void GetEvaluationProductInfo(base::OnceClosure closure,
ProductInfos expected_products,
bool result,
ShoppingCarts found_carts) {
EXPECT_EQ(1U, found_carts.size());
auto found_products = found_carts[0].second.product_infos();
EXPECT_EQ((size_t)found_products.size(), expected_products.size());
for (size_t i = 0; i < expected_products.size(); i++) {
EXPECT_EQ(found_products.at(i).product_id(),
expected_products[i].product_id());
}
std::move(closure).Run();
}
void GetEvaluationEmptyDiscount(base::OnceClosure closure,
bool result,
ShoppingCarts found) {
EXPECT_EQ(found.size(), 1U);
CartDB::KeyAndValue found_pair = found[0];
EXPECT_FALSE(found_pair.second.has_discount_info());
std::move(closure).Run();
}
void GetEvaluationDiscount(base::OnceClosure closure,
ShoppingCarts expected,
bool result,
ShoppingCarts found) {
EXPECT_EQ(found.size(), expected.size());
EXPECT_EQ(found.size(), 1U);
CartDB::KeyAndValue found_pair = found[0];
CartDB::KeyAndValue expected_pair = expected[0];
EXPECT_EQ(expected_pair.second.has_discount_info(),
found_pair.second.has_discount_info());
EXPECT_THAT(found_pair.second.discount_info(),
EqualsProto(expected_pair.second.discount_info()));
std::move(closure).Run();
}
void GetEvaluationDiscountURL(base::OnceClosure closure,
const GURL& expected,
const GURL& found) {
EXPECT_EQ(expected, found);
std::move(closure).Run();
}
std::string getDomainName(base::StringPiece domain) {
std::string* res = service_->domain_name_mapping_->FindStringKey(domain);
if (!res)
return "";
return *res;
}
std::string getDomainCartURL(base::StringPiece domain) {
std::string* res =
service_->domain_cart_url_mapping_->FindStringKey(domain);
if (!res)
return "";
return *res;
}
void CacheUsedDiscounts(const cart_db::ChromeCartContentProto& proto) {
service_->CacheUsedDiscounts(proto);
}
void CleanUpDiscounts(const cart_db::ChromeCartContentProto& proto) {
service_->CleanUpDiscounts(proto);
}
void TearDown() override {
// Clean up the used discounts dictionary prefs.
profile_->GetPrefs()->ClearPref(prefs::kCartUsedDiscounts);
}
protected:
// This needs to be destroyed after task_environment, so that any tasks on
// other threads that might check if features are enabled complete first.
base::test::ScopedFeatureList features_;
// Required to run tests from UI thread.
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
raw_ptr<CartService> service_;
};
// Verifies the hide status is flipped by hiding and restoring.
TEST_F(CartServiceTest, TestHideStatusChange) {
ASSERT_FALSE(service_->IsHidden());
service_->Hide();
ASSERT_TRUE(service_->IsHidden());
service_->RestoreHidden();
ASSERT_FALSE(service_->IsHidden());
}
// Tests adding one cart to the service.
TEST_F(CartServiceTest, TestAddCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[2];
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[0].QuitClosure(), kEmptyExpected));
run_loop[0].Run();
service_->AddCart(kMockMerchantA, absl::nullopt, kMockProtoA);
task_environment_.RunUntilIdle();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[1].QuitClosure(), kExpectedA));
run_loop[1].Run();
}
// Test updating discount for one cart.
TEST_F(CartServiceTest, TestUpdateDiscounts) {
CartDB* cart_db = service_->GetDB();
cart_db::ChromeCartContentProto proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
base::RunLoop run_loop[4];
cart_db->AddCart(
kMockMerchantA, proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[1].QuitClosure()));
run_loop[1].Run();
const double timestamp = 1;
cart_db::ChromeCartContentProto cart_with_discount_proto =
AddDiscountToProto(proto, timestamp, kMockMerchantADiscountRuleId,
kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
service_->UpdateDiscounts(GURL(kMockMerchantURLA), cart_with_discount_proto,
kNotATester);
const ShoppingCarts expected = {{kMockMerchantA, cart_with_discount_proto}};
cart_db->LoadCart(kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this),
run_loop[2].QuitClosure(), expected));
run_loop[2].Run();
CacheUsedDiscounts(cart_with_discount_proto);
service_->UpdateDiscounts(GURL(kMockMerchantURLA), cart_with_discount_proto,
kNotATester);
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[3].QuitClosure()));
run_loop[3].Run();
}
// Test adding a cart with the same key and no product image won't overwrite
// existing proto.
TEST_F(CartServiceTest, TestAddCartWithNoProductImages) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[3];
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_A_proto.set_timestamp(0);
merchant_A_proto.add_product_image_urls("https://image1.com");
merchant_A_proto.set_is_hidden(true);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
// Add a new proto with the same key and no product images.
cart_db::ChromeCartContentProto new_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
new_proto.set_timestamp(1);
service_->AddCart(kMockMerchantA, absl::nullopt, new_proto);
task_environment_.RunUntilIdle();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartHiddenStatus,
base::Unretained(this), run_loop[0].QuitClosure(), false));
run_loop[0].Run();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartTimeStamp,
base::Unretained(this), run_loop[1].QuitClosure(), 1));
run_loop[1].Run();
const ShoppingCarts result = {{kMockMerchantA, merchant_A_proto}};
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result));
run_loop[2].Run();
}
// Test adding a cart with the same key and some product images would
// overwrite the product_image_url in the existing proto.
TEST_F(CartServiceTest, TestAddCartWithProductImages) {
std::string merchant_A_discount_text = "10% off";
double new_timestamp = 1.0;
std::string new_product_image_url = "https://image2.com";
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[3];
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_A_proto.set_timestamp(0);
merchant_A_proto.add_product_image_urls("https://image1.com");
merchant_A_proto.mutable_discount_info()->set_discount_text(
merchant_A_discount_text);
merchant_A_proto.set_is_hidden(true);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
// Add a new proto with the same key and some product images.
cart_db::ChromeCartContentProto new_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
new_proto.set_timestamp(new_timestamp);
new_proto.add_product_image_urls(new_product_image_url);
service_->AddCart(kMockMerchantA, absl::nullopt, new_proto);
task_environment_.RunUntilIdle();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartHiddenStatus,
base::Unretained(this), run_loop[0].QuitClosure(), false));
run_loop[0].Run();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartTimeStamp,
base::Unretained(this), run_loop[1].QuitClosure(),
new_timestamp));
run_loop[1].Run();
merchant_A_proto.set_timestamp(new_timestamp);
merchant_A_proto.set_product_image_urls(0, new_product_image_url);
merchant_A_proto.set_is_hidden(false);
const ShoppingCarts result = {{kMockMerchantA, merchant_A_proto}};
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result));
run_loop[2].Run();
}
// Test adding a cart that has been removed would not take effect.
TEST_F(CartServiceTest, TestAddRemovedCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[3];
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_A_proto.set_timestamp(0);
merchant_A_proto.add_product_image_urls("https://image1.com");
merchant_A_proto.set_is_removed(true);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
// Add a new proto with the same key and some product images.
cart_db::ChromeCartContentProto new_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
new_proto.set_timestamp(2);
new_proto.add_product_image_urls("https://image2.com");
service_->AddCart(kMockMerchantA, absl::nullopt, new_proto);
task_environment_.RunUntilIdle();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartTimeStamp,
base::Unretained(this), run_loop[1].QuitClosure(), 0));
run_loop[1].Run();
const ShoppingCarts result = {{kMockMerchantA, merchant_A_proto}};
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result));
run_loop[2].Run();
}
TEST_F(CartServiceTest, TestAddCartWithProductInfo) {
base::RunLoop run_loop[6];
CartDB* cart_db_ = service_->GetDB();
cart_db::ChromeCartContentProto merchant_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_proto.set_timestamp(0);
merchant_proto.add_product_image_urls("https://image1.com");
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
task_environment_.RunUntilIdle();
// Adding a new proto with new product infos should reflect in storage.
cart_db::ChromeCartContentProto new_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
auto* added_product = new_proto.add_product_infos();
*added_product = kMockProductA;
new_proto.set_timestamp(1);
service_->AddCart(kMockMerchantA, absl::nullopt, new_proto);
task_environment_.RunUntilIdle();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartTimeStamp,
base::Unretained(this), run_loop[1].QuitClosure(), 1));
run_loop[1].Run();
const ShoppingCarts& expected_carts = {{kMockMerchantA, merchant_proto}};
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), expected_carts));
run_loop[2].Run();
const ProductInfos& expected_products = {kMockProductA};
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationProductInfo,
base::Unretained(this), run_loop[3].QuitClosure(),
expected_products));
run_loop[3].Run();
// Adding a new proto with same product infos shouldn't change the current
// storage about product infos.
new_proto.set_timestamp(2);
service_->AddCart(kMockMerchantA, absl::nullopt, new_proto);
task_environment_.RunUntilIdle();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartTimeStamp,
base::Unretained(this), run_loop[4].QuitClosure(), 2));
run_loop[4].Run();
cart_db_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationProductInfo,
base::Unretained(this), run_loop[5].QuitClosure(),
expected_products));
run_loop[5].Run();
}
// Tests deleting one cart from the service.
TEST_F(CartServiceTest, TestDeleteCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[4];
cart_db::ChromeCartContentProto merchant_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_proto.set_is_removed(true);
cart_db_->AddCart(
kMockMerchantA, merchant_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[1].QuitClosure(), kExpectedA));
run_loop[1].Run();
service_->DeleteCart(GURL(kMockMerchantURLA), false);
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[2].QuitClosure(), kExpectedA));
run_loop[2].Run();
service_->DeleteCart(GURL(kMockMerchantURLA), true);
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[3].QuitClosure(), kEmptyExpected));
run_loop[3].Run();
}
// Tests loading one cart from the service.
TEST_F(CartServiceTest, TestLoadCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[3];
cart_db_->AddCart(
kMockMerchantA, kMockProtoA,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->LoadCart(
kMockMerchantB,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kEmptyExpected));
run_loop[1].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), kExpectedA));
run_loop[2].Run();
}
// Tests loading all active carts from the service.
TEST_F(CartServiceTest, TestLoadAllActiveCarts) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[8];
cart_db_->AddCart(
kMockMerchantA, kMockProtoA,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kExpectedA));
run_loop[1].Run();
cart_db_->AddCart(
kMockMerchantB, kMockProtoB,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[2].QuitClosure(), true));
run_loop[2].Run();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[3].QuitClosure(), kExpectedAB));
run_loop[3].Run();
service_->HideCart(
GURL(kMockMerchantURLB),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[4].QuitClosure(), true));
run_loop[4].Run();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[5].QuitClosure(), kExpectedA));
run_loop[5].Run();
service_->RemoveCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[6].QuitClosure(), true));
run_loop[6].Run();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[7].QuitClosure(), kEmptyExpected));
run_loop[7].Run();
}
// Verifies the database is cleared when detected history deletion.
TEST_F(CartServiceTest, TestOnHistoryDeletion) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[3];
cart_db_->AddCart(
kMockMerchantA, kMockProtoA,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
task_environment_.RunUntilIdle();
run_loop[0].Run();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[1].QuitClosure(), kExpectedA));
task_environment_.RunUntilIdle();
run_loop[1].Run();
service_->OnURLsDeleted(
HistoryServiceFactory::GetForProfile(profile_.get(),
ServiceAccessType::EXPLICIT_ACCESS),
history::DeletionInfo(history::DeletionTimeRange::Invalid(), false,
history::URLRows(), std::set<GURL>(),
absl::nullopt));
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), kEmptyExpected));
task_environment_.RunUntilIdle();
run_loop[2].Run();
}
// Tests hiding a single cart and undoing the hide.
TEST_F(CartServiceTest, TestHideCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[6];
cart_db_->AddCart(
kMockMerchantA, kMockProtoA,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartHiddenStatus,
base::Unretained(this), run_loop[1].QuitClosure(), false));
run_loop[1].Run();
service_->HideCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[2].QuitClosure(), true));
run_loop[2].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartHiddenStatus,
base::Unretained(this), run_loop[3].QuitClosure(), true));
run_loop[3].Run();
service_->RestoreHiddenCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[4].QuitClosure(), true));
run_loop[4].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartHiddenStatus,
base::Unretained(this), run_loop[5].QuitClosure(), false));
run_loop[5].Run();
}
// Tests removing a single cart and undoing the remove.
TEST_F(CartServiceTest, TestRemoveCart) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[6];
cart_db_->AddCart(
kMockMerchantA, kMockProtoA,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[1].QuitClosure(), false));
run_loop[1].Run();
service_->RemoveCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[2].QuitClosure(), true));
run_loop[2].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[3].QuitClosure(), true));
run_loop[3].Run();
service_->RestoreRemovedCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[4].QuitClosure(), true));
run_loop[4].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[5].QuitClosure(), false));
run_loop[5].Run();
}
// Tests after service shutdown, content of removed cart entries are deleted
// from database except for the removed status data.
TEST_F(CartServiceTest, TestRemovedCartsDeleted) {
CartDB* cart_db_ = service_->GetDB();
base::RunLoop run_loop[6];
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_A_proto.add_product_image_urls("https://image1.com");
cart_db_->AddCart(
kMockMerchantA, merchant_A_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->RemoveCart(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[1].QuitClosure(), true));
run_loop[1].Run();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[2].QuitClosure(), kExpectedA));
run_loop[2].Run();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[3].QuitClosure(), kEmptyExpected));
run_loop[3].Run();
service_->Shutdown();
task_environment_.RunUntilIdle();
// After shut down, cart content is removed and only the removed status is
// kept.
cart_db::ChromeCartContentProto empty_proto;
empty_proto.set_key(kMockMerchantA);
empty_proto.set_is_removed(true);
const ShoppingCarts result = {{kMockMerchantA, empty_proto}};
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[4].QuitClosure(), result));
run_loop[4].Run();
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[5].QuitClosure(), true));
run_loop[5].Run();
}
// Tests whether to show the welcome surface is correctly controlled.
TEST_F(CartServiceTest, TestControlShowWelcomeSurface) {
const int limit = CartService::kWelcomSurfaceShowLimit;
for (int i = 0; i < limit; i++) {
EXPECT_EQ(i, profile_->GetPrefs()->GetInteger(
prefs::kCartModuleWelcomeSurfaceShownTimes));
EXPECT_TRUE(service_->ShouldShowWelcomeSurface());
service_->IncreaseWelcomeSurfaceCounter();
}
EXPECT_FALSE(service_->ShouldShowWelcomeSurface());
EXPECT_EQ(limit, profile_->GetPrefs()->GetInteger(
prefs::kCartModuleWelcomeSurfaceShownTimes));
}
// Tests cart data is loaded in the order of timestamp.
TEST_F(CartServiceTest, TestOrderInTimestamp) {
base::RunLoop run_loop[3];
double time_now = base::Time::Now().ToDoubleT();
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
merchant_A_proto.set_timestamp(time_now);
cart_db::ChromeCartContentProto merchant_B_proto =
BuildProto(kMockMerchantB, kMockMerchantURLB);
merchant_B_proto.set_timestamp(time_now + 1);
cart_db::ChromeCartContentProto merchant_C_proto =
BuildProto(kMockMerchantC, kMockMerchantURLC);
merchant_C_proto.set_timestamp(time_now + 2);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_A_proto);
service_->AddCart(kMockMerchantB, absl::nullopt, merchant_B_proto);
service_->AddCart(kMockMerchantC, absl::nullopt, merchant_C_proto);
task_environment_.RunUntilIdle();
const ShoppingCarts result1 = {{kMockMerchantC, merchant_C_proto},
{kMockMerchantB, merchant_B_proto},
{kMockMerchantA, merchant_A_proto}};
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[0].QuitClosure(), result1));
run_loop[0].Run();
merchant_A_proto.set_timestamp(time_now + 3);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
const ShoppingCarts result2 = {{kMockMerchantA, merchant_A_proto},
{kMockMerchantC, merchant_C_proto},
{kMockMerchantB, merchant_B_proto}};
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), result2));
run_loop[1].Run();
merchant_C_proto.set_timestamp(time_now + 4);
service_->AddCart(kMockMerchantC, absl::nullopt, merchant_C_proto);
task_environment_.RunUntilIdle();
const ShoppingCarts result3 = {{kMockMerchantC, merchant_C_proto},
{kMockMerchantA, merchant_A_proto},
{kMockMerchantB, merchant_B_proto}};
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result3));
run_loop[2].Run();
}
// Tests domain to merchant name mapping.
TEST_F(CartServiceTest, TestDomainToNameMapping) {
EXPECT_EQ("Amazon", getDomainName("amazon.com"));
EXPECT_EQ("eBay", getDomainName("ebay.com"));
EXPECT_EQ("", getDomainName("example.com"));
}
// Tests domain to cart URL mapping.
TEST_F(CartServiceTest, TestDomainToCartURLMapping) {
EXPECT_EQ("https://www.amazon.com/gp/cart/view.html",
getDomainCartURL("amazon.com"));
EXPECT_EQ("https://cart.ebay.com/", getDomainCartURL("ebay.com"));
EXPECT_EQ("", getDomainCartURL("example.com"));
}
// Tests looking up cart URL and merchant name when adding cart.
TEST_F(CartServiceTest, TestLookupCartInfo) {
CartDB* cart_db_ = service_->GetDB();
const char* amazon_domain = "amazon.com";
base::RunLoop run_loop[3];
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(amazon_domain, kMockMerchantURLA);
service_->AddCart(amazon_domain, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
merchant_A_proto.set_merchant_cart_url(getDomainCartURL(amazon_domain));
merchant_A_proto.set_merchant(getDomainName(amazon_domain));
const ShoppingCarts result1 = {{amazon_domain, merchant_A_proto}};
cart_db_->LoadCart(
amazon_domain,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), result1));
run_loop[1].Run();
// Use default value when no info can be found in the lookup table.
service_->DeleteCart(GURL(getDomainCartURL(amazon_domain)), true);
const char* fake_domain = "fake.com";
const char* fake_cart_url = "fake.com/cart";
cart_db::ChromeCartContentProto fake_proto =
BuildProto(fake_domain, fake_cart_url);
service_->AddCart(fake_domain, absl::nullopt, fake_proto);
task_environment_.RunUntilIdle();
const ShoppingCarts result2 = {{fake_domain, fake_proto}};
cart_db_->LoadCart(
fake_domain,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result2));
run_loop[2].Run();
}
// Tests the priority of cart URL sources.
TEST_F(CartServiceTest, CartURLPriority) {
const char amazon_domain[] = "amazon.com";
const char example_domain[] = "example.com";
GURL amazon_cart = GURL("http://amazon.com/mycart");
GURL amazon_cart2 = GURL("http://amazon.com/shopping-cart");
cart_db::ChromeCartContentProto merchant_A_proto =
BuildProto(amazon_domain, kMockMerchantURLA);
// The priority of shopping cart URL from highest:
// - The navigation URL when visiting carts
// - The existing URL in the cart entry if exist
// - The look-up table by eTLD+1 domain
// - The navigation URL
// * Lowest priority: no overriding.
service_->AddCart(example_domain, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(example_domain), kMockMerchantURLA);
// * Higher priority: from look up table.
service_->AddCart(amazon_domain, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain),
"https://www.amazon.com/gp/cart/view.html");
service_->DeleteCart(amazon_cart, true);
// * Higher priority: from existing entry.
service_->AddCart(amazon_domain, amazon_cart, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart.spec());
service_->AddCart(amazon_domain, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
// Lookup table cannot override existing entry.
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart.spec());
service_->DeleteCart(amazon_cart, true);
// * Highest priority: overriding existing entry.
service_->AddCart(amazon_domain, absl::nullopt, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain),
"https://www.amazon.com/gp/cart/view.html");
service_->AddCart(amazon_domain, amazon_cart, merchant_A_proto);
task_environment_.RunUntilIdle();
// Visiting carts can override existing entry.
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart.spec());
service_->DeleteCart(amazon_cart, true);
// New visiting carts can override existing entry from earlier visiting carts.
service_->AddCart(amazon_domain, amazon_cart, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart.spec());
service_->AddCart(amazon_domain, amazon_cart2, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart2.spec());
service_->AddCart(amazon_domain, amazon_cart, merchant_A_proto);
task_environment_.RunUntilIdle();
EXPECT_EQ(GetCartURL(amazon_domain), amazon_cart.spec());
}
TEST_F(CartServiceTest, TestCacheUsedDiscounts) {
EXPECT_FALSE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
cart_db::ChromeCartContentProto cart_with_discount_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), 1,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
CacheUsedDiscounts(cart_with_discount_proto);
EXPECT_TRUE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
}
TEST_F(CartServiceTest, TestCleanUpDiscounts_RuleBasedDiscount) {
cart_db::ChromeCartContentProto cart_with_discount_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), 1,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
const ShoppingCarts has_discount_cart = {
{kMockMerchantA, cart_with_discount_proto}};
CartDB* cart_db = service_->GetDB();
base::RunLoop run_loop[3];
cart_db->AddCart(
kMockMerchantA, cart_with_discount_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this), run_loop[1].QuitClosure(),
has_discount_cart));
run_loop[1].Run();
CleanUpDiscounts(cart_with_discount_proto);
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[2].QuitClosure()));
run_loop[2].Run();
}
TEST_F(CartServiceTest, TestNotCleanUpDiscounts_OtherDiscount) {
cart_db::ChromeCartContentProto cart_with_discount_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
cart_with_discount_proto.mutable_discount_info()->set_discount_text(
"10% off");
const ShoppingCarts has_discount_cart = {
{kMockMerchantA, cart_with_discount_proto}};
CartDB* cart_db = service_->GetDB();
base::RunLoop run_loop[3];
cart_db->AddCart(
kMockMerchantA, cart_with_discount_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this), run_loop[1].QuitClosure(),
has_discount_cart));
run_loop[1].Run();
CleanUpDiscounts(cart_with_discount_proto);
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this), run_loop[2].QuitClosure(),
has_discount_cart));
run_loop[2].Run();
}
TEST_F(CartServiceTest, TestUpdateDiscountsTesterByPassCachedRuleId) {
EXPECT_FALSE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
cart_db::ChromeCartContentProto cart_with_discount_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), 1,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
const ShoppingCarts has_discount_cart = {
{kMockMerchantA, cart_with_discount_proto}};
CacheUsedDiscounts(cart_with_discount_proto);
EXPECT_TRUE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
base::RunLoop run_loop[2];
CartDB* cart_db = service_->GetDB();
service_->UpdateDiscounts(GURL(kMockMerchantURLA), cart_with_discount_proto,
kNotATester);
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[0].QuitClosure()));
run_loop[0].Run();
service_->UpdateDiscounts(GURL(kMockMerchantURLA), cart_with_discount_proto,
kATester);
cart_db->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this), run_loop[1].QuitClosure(),
has_discount_cart));
run_loop[1].Run();
}
class CartServiceFakeDataTest : public CartServiceTest {
public:
// Features need to be initialized before CartServiceTest::SetUp runs, in
// order to avoid tsan data race error on FeatureList.
CartServiceFakeDataTest() {
features_.InitAndEnableFeatureWithParameters(
ntp_features::kNtpChromeCartModule,
{{"NtpChromeCartModuleDataParam", "fake"}});
}
};
TEST_F(CartServiceFakeDataTest, TestFakeData) {
base::RunLoop run_loop[2];
service_->LoadCartsWithFakeData(
base::BindOnce(&CartServiceTest::GetEvaluationFakeDataDB,
base::Unretained(this), run_loop[0].QuitClosure()));
run_loop[0].Run();
service_->Shutdown();
service_->GetDB()->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kEmptyExpected));
run_loop[1].Run();
}
// Tests expired entries are deleted when data is loaded.
TEST_F(CartServiceTest, TestExpiredDataDeleted) {
base::RunLoop run_loop[6];
cart_db::ChromeCartContentProto merchant_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
const ShoppingCarts result = {{kMockMerchantA, merchant_proto}};
merchant_proto.set_timestamp(
(base::Time::Now() - base::Days(16)).ToDoubleT());
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
task_environment_.RunUntilIdle();
// The expired entry is deleted in load results.
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[0].QuitClosure(), kEmptyExpected));
run_loop[0].Run();
// The expired entry is deleted in database.
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kEmptyExpected));
run_loop[1].Run();
// If the cart is removed, the expired entry is deleted in load results but is
// kept in database.
merchant_proto.set_is_removed(true);
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
task_environment_.RunUntilIdle();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), kEmptyExpected));
run_loop[2].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[3].QuitClosure(), result));
run_loop[3].Run();
merchant_proto.set_timestamp(
(base::Time::Now() - base::Days(13)).ToDoubleT());
merchant_proto.set_is_removed(false);
service_->GetDB()->AddCart(
kMockMerchantA, merchant_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[4].QuitClosure(), true));
run_loop[4].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationCartRemovedStatus,
base::Unretained(this), run_loop[5].QuitClosure(), false));
run_loop[5].Run();
}
// Tests cart-related actions would reshow hidden module.
TEST_F(CartServiceTest, TestHiddenFlipedByCartAction) {
base::RunLoop run_loop[3];
cart_db::ChromeCartContentProto merchant_proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
const ShoppingCarts result = {{kMockMerchantA, merchant_proto}};
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
task_environment_.RunUntilIdle();
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[0].QuitClosure(), result));
run_loop[0].Run();
service_->Hide();
ASSERT_TRUE(service_->IsHidden());
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kEmptyExpected));
run_loop[1].Run();
service_->AddCart(kMockMerchantA, absl::nullopt, merchant_proto);
task_environment_.RunUntilIdle();
ASSERT_FALSE(service_->IsHidden());
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), result));
run_loop[2].Run();
}
// Tests discount consent will never show in module without feature param.
TEST_F(CartServiceTest, TestNoShowConsentWithoutFeature) {
base::RunLoop run_loop;
for (int i = 0; i < CartService::kWelcomSurfaceShowLimit + 1; i++) {
service_->IncreaseWelcomeSurfaceCounter();
}
ASSERT_FALSE(service_->ShouldShowWelcomeSurface());
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop.QuitClosure(), false));
run_loop.Run();
}
// Tests discount is disabled without feature param.
TEST_F(CartServiceTest, TestDiscountDisabledWithoutFeature) {
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
ASSERT_FALSE(service_->IsCartDiscountEnabled());
}
// Tests acknowledging discount consent is reflected in profile pref.
TEST_F(CartServiceTest, TestAcknowledgeDiscountConsent) {
ASSERT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
ASSERT_FALSE(
profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountAcknowledged));
service_->AcknowledgeDiscountConsent(true);
ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
ASSERT_TRUE(
profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountAcknowledged));
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountAcknowledged, false);
service_->AcknowledgeDiscountConsent(false);
ASSERT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
ASSERT_TRUE(
profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountAcknowledged));
}
class MockCartDiscountLinkFetcher : public CartDiscountLinkFetcher {
public:
MOCK_METHOD(
void,
Fetch,
(std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory,
cart_db::ChromeCartContentProto cart_content_proto,
CartDiscountLinkFetcherCallback callback),
(override));
void SetDiscountURL(const GURL& discount_url) {
ON_CALL(*this, Fetch)
.WillByDefault(
[discount_url](
std::unique_ptr<network::PendingSharedURLLoaderFactory>
pending_factory,
cart_db::ChromeCartContentProto cart_content_proto,
CartDiscountLinkFetcherCallback callback) {
return std::move(callback).Run(discount_url);
});
}
};
class CartServiceDiscountTest : public CartServiceTest {
public:
// Features need to be initialized before CartServiceTest::SetUp runs, in
// order to avoid tsan data race error on FeatureList.
CartServiceDiscountTest() {
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
enabled_features;
base::FieldTrialParams cart_params, coupon_params;
cart_params["NtpChromeCartModuleAbandonedCartDiscountParam"] = "true";
cart_params["partner-merchant-pattern"] = "(foo.com)";
enabled_features.emplace_back(ntp_features::kNtpChromeCartModule,
cart_params);
coupon_params["coupon-partner-merchant-pattern"] = "(bar.com)";
enabled_features.emplace_back(commerce::kRetailCoupons, coupon_params);
features_.InitWithFeaturesAndParameters(enabled_features,
/*disabled_features*/ {});
}
void SetUp() override {
CartServiceTest::SetUp();
// Add a partner merchant cart.
service_->AddCart(kMockMerchantA, absl::nullopt, kMockProtoA);
task_environment_.RunUntilIdle();
// The feature is enabled for this test class.
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
}
void TearDown() override {
// Set the feature to default disabled state after test.
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, false);
}
void SetCartDiscountURLForTesting(const GURL& discount_url,
bool expect_call) {
std::unique_ptr<MockCartDiscountLinkFetcher> mock_fetcher =
std::make_unique<MockCartDiscountLinkFetcher>();
mock_fetcher->SetDiscountURL(discount_url);
if (expect_call) {
EXPECT_CALL(*mock_fetcher, Fetch);
}
service_->SetCartDiscountLinkFetcherForTesting(std::move(mock_fetcher));
}
};
// Tests discount consent should not show when welcome surface is still showing.
TEST_F(CartServiceDiscountTest, TestNoConsentWhenWelcomeSurface) {
base::RunLoop run_loop;
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
ASSERT_TRUE(service_->ShouldShowWelcomeSurface());
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop.QuitClosure(), false));
run_loop.Run();
}
// Tests discount consent visibility aligns with profile prefs.
TEST_F(CartServiceDiscountTest, TestReadConsentFromPrefs) {
base::RunLoop run_loop[2];
for (int i = 0; i < CartService::kWelcomSurfaceShowLimit + 1; i++) {
service_->IncreaseWelcomeSurfaceCounter();
}
ASSERT_FALSE(
profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountAcknowledged));
ASSERT_FALSE(service_->ShouldShowWelcomeSurface());
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountAcknowledged, true);
ASSERT_TRUE(
profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountAcknowledged));
ASSERT_FALSE(service_->ShouldShowWelcomeSurface());
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop[1].QuitClosure(), false));
run_loop[1].Run();
}
// Tests discount consent doesn't show when there is no partner merchant cart.
TEST_F(CartServiceDiscountTest, TestNoConsentWithoutPartnerCart) {
base::RunLoop run_loop[3];
for (int i = 0; i < CartService::kWelcomSurfaceShowLimit + 1; i++) {
service_->IncreaseWelcomeSurfaceCounter();
}
ASSERT_FALSE(service_->ShouldShowWelcomeSurface());
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->DeleteCart(GURL(kMockMerchantURLA), true);
task_environment_.RunUntilIdle();
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop[1].QuitClosure(), false));
run_loop[1].Run();
service_->AddCart(kMockMerchantB, absl::nullopt, kMockProtoB);
task_environment_.RunUntilIdle();
service_->ShouldShowDiscountConsent(
base::BindOnce(&CartServiceTest::GetEvaluationBoolResult,
base::Unretained(this), run_loop[2].QuitClosure(), true));
run_loop[2].Run();
}
// Tests updating whether rule-based discount is enabled in profile prefs.
TEST_F(CartServiceDiscountTest, TestSetCartDiscountEnabled) {
ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
service_->SetCartDiscountEnabled(false);
ASSERT_FALSE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
service_->SetCartDiscountEnabled(true);
ASSERT_TRUE(profile_->GetPrefs()->GetBoolean(prefs::kCartDiscountEnabled));
}
// Tests no fetching for discount URL if the cart is not from a partner
// merchant.
TEST_F(CartServiceDiscountTest, TestNoFetchForNonPartner) {
base::RunLoop run_loop[2];
const double timestamp = 1;
SetCartDiscountURLForTesting(GURL("https://www.discount.com"), false);
cart_db::ChromeCartContentProto cart_proto = AddDiscountToProto(
BuildProto(kMockMerchantB, kMockMerchantURLB), timestamp,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
service_->GetDB()->AddCart(
kMockMerchantB, cart_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
GURL default_cart_url(kMockMerchantURLB);
service_->GetDiscountURL(
default_cart_url,
base::BindOnce(&CartServiceTest::GetEvaluationDiscountURL,
base::Unretained(this), run_loop[1].QuitClosure(),
default_cart_url));
run_loop[1].Run();
}
// Tests no fetching for discount URL if the cart doesn't have discount info.
TEST_F(CartServiceDiscountTest, TestNoFetchWhenNoDiscount) {
base::RunLoop run_loop[2];
SetCartDiscountURLForTesting(GURL("https://www.discount.com"), false);
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[0].QuitClosure()));
run_loop[0].Run();
GURL default_cart_url(kMockMerchantURLA);
service_->GetDiscountURL(
default_cart_url,
base::BindOnce(&CartServiceTest::GetEvaluationDiscountURL,
base::Unretained(this), run_loop[1].QuitClosure(),
default_cart_url));
run_loop[1].Run();
}
// Tests no fetching for discount URL if the feature is disabled.
TEST_F(CartServiceDiscountTest, TestNoFetchWhenFeatureDisabled) {
base::RunLoop run_loop[2];
const double timestamp = 1;
GURL discount_url("https://www.discount.com");
SetCartDiscountURLForTesting(discount_url, false);
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, false);
cart_db::ChromeCartContentProto cart_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), timestamp,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
service_->GetDB()->AddCart(
kMockMerchantA, cart_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
GURL default_cart_url(kMockMerchantURLA);
service_->GetDiscountURL(
default_cart_url,
base::BindOnce(&CartServiceTest::GetEvaluationDiscountURL,
base::Unretained(this), run_loop[1].QuitClosure(),
default_cart_url));
run_loop[1].Run();
}
// Tests CartService returning fetched discount URL.
TEST_F(CartServiceDiscountTest, TestReturnDiscountURL) {
base::RunLoop run_loop[4];
const double timestamp = 1;
GURL discount_url("https://www.foo.com/discounted");
SetCartDiscountURLForTesting(discount_url, true);
EXPECT_FALSE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
cart_db::ChromeCartContentProto cart_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), timestamp,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
const ShoppingCarts has_discount_cart = {{kMockMerchantA, cart_proto}};
service_->GetDB()->AddCart(
kMockMerchantA, cart_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationDiscount,
base::Unretained(this), run_loop[1].QuitClosure(),
has_discount_cart));
run_loop[1].Run();
service_->GetDiscountURL(
GURL(kMockMerchantURLA),
base::BindOnce(&CartServiceTest::GetEvaluationDiscountURL,
base::Unretained(this), run_loop[2].QuitClosure(),
discount_url));
run_loop[2].Run();
EXPECT_TRUE(service_->IsDiscountUsed(kMockMerchantADiscountRuleId));
service_->LoadCart(
kMockMerchantA,
base::BindOnce(&CartServiceTest::GetEvaluationEmptyDiscount,
base::Unretained(this), run_loop[3].QuitClosure()));
run_loop[3].Run();
}
// Tests CartService returning original cart URL as a fallback if the fetch
// response is invalid.
TEST_F(CartServiceDiscountTest, TestFetchInvalidFallback) {
base::RunLoop run_loop[2];
const double timestamp = 1;
SetCartDiscountURLForTesting(GURL("error"), true);
cart_db::ChromeCartContentProto cart_proto = AddDiscountToProto(
BuildProto(kMockMerchantA, kMockMerchantURLA), timestamp,
kMockMerchantADiscountRuleId, kMockMerchantADiscountsPercentOff,
kMockMerchantADiscountsRawMerchantOfferId);
service_->GetDB()->AddCart(
kMockMerchantA, cart_proto,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
GURL default_cart_url(kMockMerchantURLA);
service_->GetDiscountURL(
default_cart_url,
base::BindOnce(&CartServiceTest::GetEvaluationDiscountURL,
base::Unretained(this), run_loop[1].QuitClosure(),
default_cart_url));
run_loop[1].Run();
}
class CartServiceSkipExtractionTest : public CartServiceTest {
public:
// Features need to be initialized before CartServiceTest::SetUp runs, in
// order to avoid tsan data race error on FeatureList.
CartServiceSkipExtractionTest() {
features_.InitAndEnableFeatureWithParameters(
ntp_features::kNtpChromeCartModule,
{{"skip-cart-extraction-pattern", kMockMerchantC}});
}
};
TEST_F(CartServiceSkipExtractionTest, TestAddCartForSkippedMerchants) {
base::RunLoop run_loop[4];
CartDB* cart_db_ = service_->GetDB();
// Product images are not stored for skipped merchants.
service_->AddCart(kMockMerchantC, absl::nullopt, kMockProtoCWithProduct);
task_environment_.RunUntilIdle();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[0].QuitClosure(), kExpectedC));
run_loop[0].Run();
// Product images are overwritten for skipped merchants.
cart_db_->AddCart(
kMockMerchantC, kMockProtoCWithProduct,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[1].QuitClosure(), true));
run_loop[1].Run();
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), kExpectedCWithProduct));
run_loop[2].Run();
service_->AddCart(kMockMerchantC, absl::nullopt, kMockProtoCWithProduct);
task_environment_.RunUntilIdle();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[3].QuitClosure(), kExpectedC));
run_loop[3].Run();
}
TEST_F(CartServiceSkipExtractionTest, TestLoadCartForSkippedMerchants) {
base::RunLoop run_loop[4];
CartDB* cart_db_ = service_->GetDB();
cart_db_->AddCart(
kMockMerchantC, kMockProtoCWithProduct,
base::BindOnce(&CartServiceTest::OperationEvaluation,
base::Unretained(this), run_loop[0].QuitClosure(), true));
run_loop[0].Run();
cart_db_->LoadAllCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[1].QuitClosure(), kExpectedCWithProduct));
run_loop[1].Run();
// Skipped carts will not show product images when loading, and the existing
// product images in skipped carts will also be cleared.
service_->LoadAllActiveCarts(
base::BindOnce(&CartServiceTest::GetEvaluationURL, base::Unretained(this),
run_loop[2].QuitClosure(), kExpectedC));
run_loop[2].Run();
cart_db_->LoadAllCarts(base::BindOnce(&CartServiceTest::GetEvaluationURL,
base::Unretained(this),
run_loop[3].QuitClosure(), kExpectedC));
run_loop[3].Run();
}
class CartServiceRbdFastPathTest : public CartServiceTest {
public:
CartServiceRbdFastPathTest() {
// This needs to be called before any tasks that run on other threads check
// if a feature is enabled.
features_.InitAndEnableFeatureWithParameters(
ntp_features::kNtpChromeCartModule,
{{"NtpChromeCartModuleAbandonedCartDiscountParam", "true"},
{"partner-merchant-pattern", "(foo.com)"},
{ntp_features::kNtpChromeCartModuleAbandonedCartDiscountUseUtmParam,
"true"}});
}
void TearDown() override {
// Set the feature to default disabled state after test.
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, false);
}
};
TEST_F(CartServiceRbdFastPathTest, TestAppendUTM) {
EXPECT_FALSE(service_->IsCartDiscountEnabled());
EXPECT_EQ(GURL("https://www.foo.com?utm_source=chrome_cart_no_rbd"),
CartService::AppendUTM(GURL(kMockMerchantURLA), false));
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
EXPECT_TRUE(service_->IsCartDiscountEnabled());
EXPECT_EQ(GURL("https://www.foo.com?utm_source=chrome_cart_rbd"),
CartService::AppendUTM(GURL(kMockMerchantURLA), true));
}
TEST_F(CartServiceRbdFastPathTest, TestAppendUTMAvoidDuplicates) {
std::string merchantUrl = "https://www.foo.com";
EXPECT_FALSE(service_->IsCartDiscountEnabled());
EXPECT_EQ(GURL("https://www.foo.com?utm_source=chrome_cart_no_rbd"),
CartService::AppendUTM(GURL(kMockMerchantURLA), false));
merchantUrl = "https://www.foo.com?utm_source=chrome_cart_no_rbd";
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
EXPECT_TRUE(service_->IsCartDiscountEnabled());
EXPECT_EQ(GURL("https://www.foo.com?utm_source=chrome_cart_rbd"),
CartService::AppendUTM(GURL(kMockMerchantURLA), true));
}
class FakeFetchDiscountWorker : public FetchDiscountWorker {
public:
FakeFetchDiscountWorker(
scoped_refptr<network::SharedURLLoaderFactory>
browserProcessURLLoaderFactory,
std::unique_ptr<CartDiscountFetcherFactory> fetcher_factory,
std::unique_ptr<CartServiceDelegate> cart_service_delegate,
signin::IdentityManager* const identity_manager,
variations::VariationsClient* const chrome_variations_client)
: FetchDiscountWorker(browserProcessURLLoaderFactory,
std::move(fetcher_factory),
std::move(cart_service_delegate),
identity_manager,
chrome_variations_client) {}
// Simulate FetchDiscountWorker posting a task to fetch, except that here we
// only record fetch timestamp instead of actually fetching.
void Start(base::TimeDelta delay) override {
content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
->PostDelayedTask(FROM_HERE,
base::BindOnce(&FakeFetchDiscountWorker::FakeFetch,
weak_ptr_factory_.GetWeakPtr()),
delay);
}
private:
void FakeFetch() { cart_service_delegate_->RecordFetchTimestamp(); }
base::WeakPtrFactory<FakeFetchDiscountWorker> weak_ptr_factory_{this};
};
class CartServiceDiscountFetchTest : public CartServiceTest {
public:
CartServiceDiscountFetchTest() {
// This needs to be called before any tasks that run on other threads check
// if a feature is enabled.
features_.InitAndEnableFeatureWithParameters(
ntp_features::kNtpChromeCartModule,
{{"NtpChromeCartModuleAbandonedCartDiscountParam", "true"},
{"discount-fetch-delay", "2s"}});
}
void SetUp() override {
CartServiceTest::SetUp();
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
// Only initialize CartServiceDelegate which is relevant to this test.
fetch_discount_worker_ = std::make_unique<FakeFetchDiscountWorker>(
nullptr, nullptr, std::make_unique<CartServiceDelegate>(service_),
nullptr, nullptr);
service_->SetFetchDiscountWorkerForTesting(
std::move(fetch_discount_worker_));
}
void TearDown() override {
// Reset the last fetch timestamp.
profile_->GetPrefs()->SetTime(prefs::kCartDiscountLastFetchedTime,
base::Time());
// Reset FetchDiscountWorker for testing.
service_->SetFetchDiscountWorkerForTesting(nullptr);
}
void StartGettingDiscount() { service_->StartGettingDiscount(); }
private:
std::unique_ptr<FakeFetchDiscountWorker> fetch_discount_worker_;
};
TEST_F(CartServiceDiscountFetchTest, TestFreshFetch) {
EXPECT_EQ(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
base::Time());
StartGettingDiscount();
task_environment_.RunUntilIdle();
EXPECT_NE(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
base::Time());
}
TEST_F(CartServiceDiscountFetchTest, TestFetchWhenBeyondEnforcedDelay) {
// Set last fetch timestamp so that the current time is beyond the enforced
// delay.
base::Time last_fetch_time = base::Time::Now() - base::Seconds(20);
profile_->GetPrefs()->SetTime(prefs::kCartDiscountLastFetchedTime,
last_fetch_time);
StartGettingDiscount();
task_environment_.RunUntilIdle();
EXPECT_NE(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
last_fetch_time);
}
TEST_F(CartServiceDiscountFetchTest, TestNoFetchWithinEnforcedDelay) {
EXPECT_EQ(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
base::Time());
base::Time last_fetch_time = base::Time::Now();
profile_->GetPrefs()->SetTime(prefs::kCartDiscountLastFetchedTime,
last_fetch_time);
// Set last fetch timestamp so that the current time is within the enforced
// delay.
StartGettingDiscount();
task_environment_.RunUntilIdle();
EXPECT_EQ(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
last_fetch_time);
// Wait so that the current time is beyond the enforced delay.
task_environment_.FastForwardBy(base::Seconds(2));
StartGettingDiscount();
task_environment_.RunUntilIdle();
EXPECT_NE(profile_->GetPrefs()->GetTime(prefs::kCartDiscountLastFetchedTime),
last_fetch_time);
}
class CartServiceCouponTest : public CartServiceTest {
public:
void SetUp() override {
CartServiceTest::SetUp();
SetCouponServiceForTesting(&coupon_service_);
}
protected:
class MockCouponService : public CouponService {
public:
MOCK_METHOD0(DeleteAllFreeListingCoupons, void(void));
MOCK_METHOD1(DeleteFreeListingCouponsForUrl, void(const GURL& url));
MOCK_METHOD1(MaybeFeatureStatusChanged, void(bool enabled));
};
void SetCouponServiceForTesting(CouponService* coupon_service) {
service_->SetCouponServiceForTesting(coupon_service);
}
MockCouponService coupon_service_;
};
TEST_F(CartServiceCouponTest, TestDeleteCartWithCoupon) {
const GURL& url = GURL(kMockMerchantURLA);
EXPECT_CALL(coupon_service_, DeleteFreeListingCouponsForUrl(url)).Times(2);
service_->DeleteCart(url, true);
service_->DeleteCart(url, false);
}
TEST_F(CartServiceCouponTest, TestClearCoupons) {
EXPECT_CALL(coupon_service_, DeleteAllFreeListingCoupons()).Times(1);
service_->OnURLsDeleted(
HistoryServiceFactory::GetForProfile(profile_.get(),
ServiceAccessType::EXPLICIT_ACCESS),
history::DeletionInfo(history::DeletionTimeRange::Invalid(), false,
history::URLRows(), std::set<GURL>(),
absl::nullopt));
}
TEST_F(CartServiceCouponTest, TestUpdateCartDeleteCoupon_AddProduct) {
const GURL& url = GURL(kMockMerchantURLA);
EXPECT_CALL(coupon_service_, DeleteFreeListingCouponsForUrl(url)).Times(0);
// Construct a proto with one product.
cart_db::ChromeCartContentProto proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
proto.add_product_image_urls("https://image1.com");
auto* added_product = proto.add_product_infos();
*added_product = kMockProductA;
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
// A new cart added with new products will not delete coupons.
added_product = proto.add_product_infos();
*added_product = kMockProductB;
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
}
TEST_F(CartServiceCouponTest, TestUpdateCartDeleteCoupon_DeleteProduct) {
const GURL& url = GURL(kMockMerchantURLA);
EXPECT_CALL(coupon_service_, DeleteFreeListingCouponsForUrl(url)).Times(1);
// Construct a proto with two products.
cart_db::ChromeCartContentProto proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
proto.add_product_image_urls("https://image1.com");
auto* added_product = proto.add_product_infos();
*added_product = kMockProductA;
added_product = proto.add_product_infos();
*added_product = kMockProductB;
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
// A new cart added with one product removed will trigger coupon deletion.
proto.clear_product_infos();
added_product = proto.add_product_infos();
*added_product = kMockProductA;
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
}
TEST_F(CartServiceCouponTest, TestUpdateCartDeleteCoupon_ReplaceProduct) {
const GURL& url = GURL(kMockMerchantURLA);
EXPECT_CALL(coupon_service_, DeleteFreeListingCouponsForUrl(url)).Times(1);
// Construct a proto with two products.
cart_db::ChromeCartContentProto proto =
BuildProto(kMockMerchantA, kMockMerchantURLA);
proto.add_product_image_urls("https://image1.com");
auto* added_product = proto.add_product_infos();
*added_product = kMockProductA;
added_product = proto.add_product_infos();
*added_product = kMockProductB;
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
// A new cart added with one product replaced will trigger coupon deletion.
proto.clear_product_infos();
added_product = proto.add_product_infos();
*added_product = kMockProductA;
added_product = proto.add_product_infos();
*added_product = BuildProductProto("id_qux");
service_->AddCart(kMockMerchantA, absl::nullopt, proto);
task_environment_.RunUntilIdle();
}
TEST_F(CartServiceCouponTest, TestRBDFeatureStatusUpdate) {
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(false)).Times(1);
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, false);
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(true)).Times(1);
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
}
TEST_F(CartServiceCouponTest, TestCartFeatureStatusUpdate) {
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(false)).Times(2);
ListPrefUpdate(profile_->GetPrefs(), prefs::kNtpDisabledModules)
->Append(base::Value("chrome_cart"));
ListPrefUpdate(profile_->GetPrefs(), prefs::kNtpDisabledModules)
->Append(base::Value("something_unrelated"));
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(true)).Times(1);
ListPrefUpdate(profile_->GetPrefs(), prefs::kNtpDisabledModules)
->EraseListValue(base::Value("chrome_cart"));
}
TEST_F(CartServiceCouponTest, TestModuleFeatureStatusUpdate) {
// prefs::kNtpModulesVisible is true by default.
profile_->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true);
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(false)).Times(1);
profile_->GetPrefs()->SetBoolean(prefs::kNtpModulesVisible, false);
EXPECT_CALL(coupon_service_, MaybeFeatureStatusChanged(true)).Times(1);
profile_->GetPrefs()->SetBoolean(prefs::kNtpModulesVisible, true);
}
class CartServiceModulesRedesignedTest : public CartServiceTest {
public:
CartServiceModulesRedesignedTest() {
features_.InitAndEnableFeature(ntp_features::kNtpModulesRedesigned);
}
};
// Verifies the hide status is ignored.
TEST_F(CartServiceModulesRedesignedTest, TestIgnoresHidden) {
ASSERT_FALSE(service_->IsHidden());
service_->Hide();
ASSERT_FALSE(service_->IsHidden());
}