blob: eb8b88e9c4ca0e21d143e5e82d973f5cf3aa961d [file] [log] [blame]
// Copyright 2021 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_discount_fetcher.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/cart/fetch_discount_worker.h"
#include "chrome/browser/endpoint_fetcher/endpoint_fetcher.h"
#include "chrome/browser/persisted_state_db/profile_proto_db.h"
#include "components/search/ntp_features.h"
#include "content/public/test/browser_task_environment.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
cart_db::ChromeCartContentProto BuildProto(
const char* domain,
const char* merchant_url,
const double timestamp,
const std::vector<std::string>& raw_merchant_offers) {
cart_db::ChromeCartContentProto proto;
proto.set_key(domain);
proto.set_merchant_cart_url(merchant_url);
proto.set_timestamp(timestamp);
for (const std::string& offer : raw_merchant_offers) {
proto.add_product_infos()->set_product_id(offer);
}
return proto;
}
using ShoppingCarts =
std::vector<ProfileProtoDB<cart_db::ChromeCartContentProto>::KeyAndValue>;
const char kMockMerchantA[] = "foo.com";
const char kMockMerchantCartURLA[] = "https://www.foo.com/cart";
const char kMockMerchantAHighestDiscountString[] = "Up to $10 off";
const int kMockMerchantALastTimestamp = 1;
const int kThirtyMinutesInSeconds = 1800;
const char kMockMerchantARawOfferIdOne[] = "offerId1";
const char kMockMerchantARawOfferIdTwo[] = "offerId2";
const char kConfigurableEndpoint[] = "https://testing_endpoint.com/discounts";
const char kLocales[] = "en-US";
const char kVariationHeaders[] = "variationsHeaders";
const char kEndpointResponse[] =
"{ "
" \"discounts\": [ "
" { "
" \"merchantIdentifier\": { "
" \"cartUrl\": \"https://www.foo.com/cart\", "
" \"merchantId\": \"0\" "
" }, "
" \"ruleDiscounts\": [ "
" { "
" \"ruleId\": \"0\", "
" \"discount\": { "
" \"percentOff\": 10 "
" }, "
" \"merchantRuleId\": \"1\", "
" \"rawMerchantOfferId\": \"offerId1\" "
" }, "
" { "
" \"ruleId\": \"1\", "
" \"discount\": { "
" \"amountOff\": { "
" \"currencyCode\": \"USD\", "
" \"units\": \"10\", "
" \"nanos\": 0 "
" } "
" }, "
" \"merchantRuleId\": \"1\", "
" \"rawMerchantOfferId\": \"offerId2\" "
" } "
" ] "
" } "
" ] "
"} ";
} // namespace
class MockEndpointFetcher : public EndpointFetcher {
public:
explicit MockEndpointFetcher(
const net::NetworkTrafficAnnotationTag& annotation_tag)
: EndpointFetcher(annotation_tag) {}
MOCK_METHOD(void,
PerformRequest,
(EndpointFetcherCallback endpoint_fetcher_callback,
const char* key),
(override));
};
class CartDiscountFetcherTest {
public:
static std::string generatePostData(
std::vector<CartDB::KeyAndValue> proto_pairs,
double current_timestamp) {
return CartDiscountFetcher::generatePostData(
std::move(proto_pairs), base::Time::FromDoubleT(current_timestamp));
}
static std::unique_ptr<EndpointFetcher> CreateEndpointFetcher(
std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory,
std::vector<CartDB::KeyAndValue> proto_pairs,
bool is_oauth_fetch,
const std::string fetch_for_locale) {
return CartDiscountFetcher::CreateEndpointFetcher(
std::move(pending_factory), std::move(proto_pairs), is_oauth_fetch,
fetch_for_locale, kVariationHeaders);
}
static void OnDiscountsAvailable(
std::unique_ptr<EndpointFetcher> endpoint_fetcher,
CartDiscountFetcher::CartDiscountFetcherCallback callback,
std::unique_ptr<EndpointResponse> responses) {
CartDiscountFetcher::OnDiscountsAvailable(
std::move(endpoint_fetcher), std::move(callback), std::move(responses));
}
};
TEST(CartDiscountFetcherTest, TestGeneratePostData) {
ShoppingCarts shoppingcartA = {
{kMockMerchantA,
BuildProto(kMockMerchantA, kMockMerchantCartURLA,
kMockMerchantALastTimestamp,
{kMockMerchantARawOfferIdOne, kMockMerchantARawOfferIdTwo})}};
// Expected json string:
// "{"
// " \"carts\": ["
// " {"
// " \"cartAbandonedTimeMinutes\": 30,"
// " \"merchantIdentifier\": {"
// " \"cartUrl\": \"www.foo.com/cart\","
// " },"
// " \"rawMerchantOffers\": ["
// " \"offerId1\","
// " \"offerId2\}"
// " ]"
// " }"
// " ]"
// "}"
std::string expected =
"{\"carts\":[{\"cartAbandonedTimeMinutes\":30,\"merchantIdentifier\":{"
"\"cartUrl\":\"https://www.foo.com/"
"cart\"},\"rawMerchantOffers\":[\"offerId1\",\"offerId2\"]}]}";
int abandondTimeInSeconds =
kMockMerchantALastTimestamp + kThirtyMinutesInSeconds; // 30 Minutes
std::string result = CartDiscountFetcherTest::generatePostData(
std::move(shoppingcartA), abandondTimeInSeconds);
EXPECT_EQ(expected, result);
}
TEST(CartDiscountFetcherTest, TestOnDiscountsAvailableParsing) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response = kEndpointResponse;
// TODO(meiliang): Test the callback argument.
EXPECT_CALL(mock_callback, Run(testing::_, testing::_));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
}
TEST(CartDiscountFetcherTest, TestHighestDiscounts) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response = kEndpointResponse;
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, testing::_))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(cart_discount_map.at(kMockMerchantCartURLA).highest_discount_string,
kMockMerchantAHighestDiscountString);
}
TEST(CartDiscountFetcherTest, TestRawMaerchantOffersIsOptional) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response =
"{ "
" \"discounts\": [ "
" { "
" \"merchantIdentifier\": { "
" \"cartUrl\": \"https://www.foo.com/cart\", "
" \"merchantId\": \"0\" "
" }, "
" \"ruleDiscounts\": [ "
" { "
" \"ruleId\": \"0\", "
" \"discount\": { "
" \"percentOff\": 10 "
" }, "
" \"merchantRuleId\": \"1\" "
" } "
" ] "
" } "
" ] "
"} ";
const bool expected_not_a_tester = false;
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, expected_not_a_tester))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(
cart_discount_map.at(kMockMerchantCartURLA).rule_discount_list.size(),
1u);
EXPECT_TRUE(cart_discount_map.at(kMockMerchantCartURLA)
.rule_discount_list[0]
.raw_merchant_offer_id()
.empty());
}
TEST(CartDiscountFetcherTest, TestExternalTesterDiscount) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response =
"{ "
" \"discounts\": [ "
" { "
" \"merchantIdentifier\": { "
" \"cartUrl\": \"https://www.foo.com/cart\", "
" \"merchantId\": \"0\" "
" }, "
" \"ruleDiscounts\": [ "
" { "
" \"ruleId\": \"0\", "
" \"discount\": { "
" \"percentOff\": 10 "
" }, "
" \"merchantRuleId\": \"1\" "
" } "
" ] "
" } "
" ], "
" \"externalTester\": true "
"} ";
const bool expected_a_tester = true;
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, expected_a_tester))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(
cart_discount_map.at(kMockMerchantCartURLA).rule_discount_list.size(),
1u);
EXPECT_TRUE(cart_discount_map.at(kMockMerchantCartURLA)
.rule_discount_list[0]
.raw_merchant_offer_id()
.empty());
}
TEST(CartDiscountFetcherTest, TestNoRuleDiscounts) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response = R"(
{
"discounts": [
{
"merchantIdentifier": {
"cartUrl": "https://www.foo.com/cart",
"merchantId": "0"
}
}
]
}
)";
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, testing::_))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(
cart_discount_map.at(kMockMerchantCartURLA).rule_discount_list.size(),
0u);
}
TEST(CartDiscountFetcherTest, TestOverallDiscountText) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response = R"(
{
"discounts": [
{
"merchantIdentifier": {
"cartUrl": "https://www.foo.com/cart",
"merchantId": "0"
},
"overallDiscountInfo": {
"text": "10% off"
}
}
]
}
)";
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, testing::_))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(cart_discount_map.at(kMockMerchantCartURLA).highest_discount_string,
"10% off");
}
TEST(CartDiscountFetcherTest, TestOverallDiscountTextWithRuleDiscounts) {
std::unique_ptr<EndpointFetcher> mock_endpoint_fetcher =
std::make_unique<MockEndpointFetcher>(TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<CartDiscountFetcher::CartDiscountFetcherCallback>
mock_callback;
auto fake_responses = std::make_unique<EndpointResponse>();
fake_responses->response = R"(
{
"discounts": [
{
"merchantIdentifier": {
"cartUrl": "https://www.foo.com/cart",
"merchantId": "0"
},
"overallDiscountInfo": {
"text": "20% off"
},
"ruleDiscounts": [
{
"ruleId": "0",
"discount": {
"percentOff": 10
},
"merchantRuleId": "1"
}
]
}
]
}
)";
CartDiscountFetcher::CartDiscountMap cart_discount_map;
EXPECT_CALL(mock_callback, Run(testing::_, testing::_))
.WillOnce(testing::SaveArg<0>(&cart_discount_map));
CartDiscountFetcherTest::OnDiscountsAvailable(
std::move(mock_endpoint_fetcher), mock_callback.Get(),
std::move(fake_responses));
EXPECT_EQ(cart_discount_map.size(), 1u);
EXPECT_EQ(cart_discount_map.at(kMockMerchantCartURLA).highest_discount_string,
"20% off");
}
class CartDiscountFetcherConfigurableEndpointTest : public testing::Test {
public:
// Features need to be initialized before CartServiceTest::SetUp runs, in
// order to avoid tsan data race error on FeatureList.
CartDiscountFetcherConfigurableEndpointTest() {
features_.InitAndEnableFeatureWithParameters(
ntp_features::kNtpChromeCartModule,
{{"CartDiscountFetcherEndpointParam", kConfigurableEndpoint}});
}
protected:
base::test::ScopedFeatureList features_;
content::BrowserTaskEnvironment task_environment_;
};
TEST_F(CartDiscountFetcherConfigurableEndpointTest,
TestServerConfiguraleEndpoint) {
scoped_refptr<network::TestSharedURLLoaderFactory> shared_url_loader_factory =
base::MakeRefCounted<network::TestSharedURLLoaderFactory>();
std::unique_ptr<EndpointFetcher> endpoint_fetcher =
CartDiscountFetcherTest::CreateEndpointFetcher(
shared_url_loader_factory->Clone(), {}, false /*is_oauth_fetch*/,
kLocales);
EXPECT_EQ(endpoint_fetcher->GetUrlForTesting(), kConfigurableEndpoint);
}