| // 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/fetch_discount_worker.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "chrome/browser/cart/cart_discount_fetcher.h" |
| #include "chrome/browser/endpoint_fetcher/endpoint_fetcher.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/search/ntp_features.h" |
| #include "components/signin/public/identity_manager/identity_test_environment.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_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| cart_db::DiscountInfoProto BuildPercentOffDiscountInfoProto( |
| const std::string& rule_id, |
| const std::string& merchant_rule_id, |
| const std::string& raw_merchant_offer_id, |
| const int percent_off) { |
| cart_db::DiscountInfoProto proto; |
| proto.set_rule_id(rule_id); |
| proto.set_merchant_rule_id(merchant_rule_id); |
| proto.set_percent_off(percent_off); |
| proto.set_raw_merchant_offer_id(raw_merchant_offer_id); |
| return proto; |
| } |
| |
| cart_db::ChromeCartContentProto BuildCartContentProto(const char* domain, |
| const char* merchant_url, |
| const double timestamp) { |
| cart_db::ChromeCartContentProto proto; |
| proto.set_key(domain); |
| proto.set_merchant_cart_url(merchant_url); |
| proto.set_timestamp(timestamp); |
| return proto; |
| } |
| |
| cart_db::ChromeCartContentProto AddDiscountToProto( |
| cart_db::ChromeCartContentProto proto, |
| const std::string& merchant_id, |
| cart_db::DiscountInfoProto discount_proto) { |
| proto.mutable_discount_info()->set_merchant_id(merchant_id); |
| (*(proto.mutable_discount_info()->add_discount_info())) = discount_proto; |
| 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; |
| } |
| |
| const char kMockMerchantA[] = "foo.com"; |
| const char kMockMerchantACartUrl[] = "https://www.foo.com/cart"; |
| const char kMockMerchantAId[] = "123"; |
| const char kMockMerchantARuleId[] = "456"; |
| const char kMockMerchantARawMerchantOfferId[] = "789"; |
| const char kMockMerchantAHighestPercentOff[] = "10\% off"; |
| const int kMockMerchantAPercentOff = 10; |
| const double kMockMerchantATimestamp = base::Time::Now().ToDoubleT(); |
| const cart_db::ChromeCartContentProto kMockMerchantACartContentProto = |
| BuildCartContentProto(kMockMerchantA, |
| kMockMerchantACartUrl, |
| kMockMerchantATimestamp); |
| const std::vector<cart_db::DiscountInfoProto> kMockMerchantADiscounts = { |
| BuildPercentOffDiscountInfoProto(kMockMerchantARuleId, |
| kMockMerchantARuleId, |
| kMockMerchantARawMerchantOfferId, |
| kMockMerchantAPercentOff)}; |
| const char kEmail[] = "mock_email@gmail.com"; |
| } // namespace |
| |
| class FakeCartDiscountFetcher : public CartDiscountFetcher { |
| public: |
| void Fetch( |
| std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory, |
| CartDiscountFetcherCallback callback, |
| std::vector<CartDB::KeyAndValue> proto_pairs, |
| const bool is_oauth_fetch, |
| const std::string access_token_str) override { |
| FakeCartDiscountFetcher::fetcher_fetch_count_++; |
| // Only oauth fetch has a chance to be a tester. |
| bool is_tester = is_tester_ && is_oauth_fetch; |
| std::move(callback).Run(fake_result_, is_tester); |
| } |
| |
| void SetFakeFetcherResult(CartDiscountFetcher::CartDiscountMap fake_result) { |
| fake_result_ = std::move(fake_result); |
| } |
| |
| void SetExpectedTester(bool is_tester) { is_tester_ = is_tester; } |
| |
| static int GetFetchCount() { return fetcher_fetch_count_; } |
| |
| static void ClearFetchCount() { fetcher_fetch_count_ = 0; } |
| |
| private: |
| CartDiscountFetcher::CartDiscountMap fake_result_; |
| static int fetcher_fetch_count_; |
| bool is_tester_{false}; |
| }; |
| |
| int FakeCartDiscountFetcher::fetcher_fetch_count_ = 0; |
| |
| class MockCartDiscountFetcher : public CartDiscountFetcher { |
| public: |
| MOCK_METHOD( |
| void, |
| Fetch, |
| (std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_factory, |
| CartDiscountFetcherCallback callback, |
| std::vector<CartDB::KeyAndValue> proto_pairs, |
| const bool is_oauth_fetch, |
| const std::string access_token_str), |
| (override)); |
| |
| void DelegateToFake(CartDiscountMap fake_result, bool is_tester) { |
| fake_cart_discount_fetcher_.SetFakeFetcherResult(std::move(fake_result)); |
| fake_cart_discount_fetcher_.SetExpectedTester(is_tester); |
| |
| EXPECT_CALL(*this, Fetch) |
| .WillRepeatedly( |
| [this](std::unique_ptr<network::PendingSharedURLLoaderFactory> |
| pending_factory, |
| CartDiscountFetcherCallback callback, |
| std::vector<CartDB::KeyAndValue> proto_pairs, |
| const bool is_oauth_fetch, |
| const std::string access_token_str) { |
| return fake_cart_discount_fetcher_.Fetch( |
| std::move(pending_factory), std::move(callback), |
| std::move(proto_pairs), is_oauth_fetch, |
| std::move(access_token_str)); |
| }); |
| } |
| |
| private: |
| FakeCartDiscountFetcher fake_cart_discount_fetcher_; |
| }; |
| |
| class FakeCartDiscountFetcherFactory : public CartDiscountFetcherFactory { |
| public: |
| std::unique_ptr<CartDiscountFetcher> createFetcher() override { |
| auto fetcher = std::make_unique<MockCartDiscountFetcher>(); |
| fetcher->DelegateToFake(fetcher_fake_result_, is_tester_); |
| return std::move(fetcher); |
| } |
| |
| void SetFetcherFakeResult( |
| CartDiscountFetcher::CartDiscountMap fetcher_fake_result) { |
| fetcher_fake_result_ = std::move(fetcher_fake_result); |
| } |
| |
| void SetExpectedTester(bool is_tester) { is_tester_ = is_tester; } |
| |
| private: |
| CartDiscountFetcher::CartDiscountMap fetcher_fake_result_; |
| bool is_tester_{false}; |
| }; |
| |
| class FakeCartLoader : public CartLoader { |
| public: |
| explicit FakeCartLoader(Profile* profile) : CartLoader(profile) {} |
| |
| void LoadAllCarts(CartDB::LoadCallback callback) override { |
| std::move(callback).Run(true, fake_cart_data_); |
| } |
| |
| void setFakeCartData(std::vector<CartDB::KeyAndValue> proto_pairs) { |
| fake_cart_data_ = std::move(proto_pairs); |
| } |
| |
| private: |
| std::vector<CartDB::KeyAndValue> fake_cart_data_; |
| }; |
| |
| class FakeCartDiscountUpdater : public CartDiscountUpdater { |
| public: |
| explicit FakeCartDiscountUpdater(Profile* profile) |
| : CartDiscountUpdater(profile), expected_tester_(false) {} |
| |
| void SetExpectedData( |
| cart_db::ChromeCartContentProto fake_updater_expected_data, |
| bool has_discounts, |
| std::string& expected_highest_discount_string, |
| bool is_tester) { |
| expected_update_data_ = fake_updater_expected_data; |
| has_discounts_ = has_discounts; |
| expected_highest_discount_string_ = expected_highest_discount_string; |
| expected_tester_ = is_tester; |
| } |
| |
| void update(const std::string& cart_url, |
| const cart_db::ChromeCartContentProto new_proto, |
| const bool is_tester) override { |
| // Verify discount_info. |
| int new_proto_discount_size = |
| new_proto.discount_info().discount_info_size(); |
| EXPECT_EQ(new_proto_discount_size, |
| expected_update_data_.discount_info().discount_info_size()); |
| |
| EXPECT_EQ(new_proto_discount_size != 0, has_discounts_); |
| |
| for (int i = 0; i < new_proto_discount_size; i++) { |
| EXPECT_THAT( |
| new_proto.discount_info().discount_info(i), |
| EqualsProto(expected_update_data_.discount_info().discount_info(i))); |
| } |
| |
| const std::string& discount_text = |
| new_proto.discount_info().discount_text(); |
| if (has_discounts_) { |
| EXPECT_EQ(discount_text, expected_highest_discount_string_); |
| } else { |
| EXPECT_TRUE(discount_text.empty()); |
| } |
| |
| EXPECT_EQ(is_tester, expected_tester_); |
| } |
| |
| private: |
| cart_db::ChromeCartContentProto expected_update_data_; |
| bool has_discounts_; |
| std::string expected_highest_discount_string_; |
| bool expected_tester_; |
| }; |
| |
| class FakeCartLoaderAndUpdaterFactory : public CartLoaderAndUpdaterFactory { |
| public: |
| explicit FakeCartLoaderAndUpdaterFactory(Profile* profile) |
| : CartLoaderAndUpdaterFactory(profile), |
| profile_(profile), |
| expected_tester_(false) {} |
| |
| std::unique_ptr<CartLoader> createCartLoader() override { |
| auto fake_loader = std::make_unique<FakeCartLoader>(profile_); |
| fake_loader->setFakeCartData(fake_loader_data_); |
| return fake_loader; |
| } |
| std::unique_ptr<CartDiscountUpdater> createCartDiscountUpdater() override { |
| auto fake_updater = std::make_unique<FakeCartDiscountUpdater>(profile_); |
| fake_updater->SetExpectedData( |
| fake_updater_expected_data_, fake_updater_has_discounts_, |
| fake_updater_highest_discount_string_, expected_tester_); |
| return fake_updater; |
| } |
| |
| void setCartLoaderFakeData(std::vector<CartDB::KeyAndValue> fake_data) { |
| fake_loader_data_ = fake_data; |
| } |
| |
| void setCartDiscountUpdaterExpectedData( |
| cart_db::ChromeCartContentProto fake_updater_expected_data, |
| bool has_discounts, |
| base::StringPiece fake_updater_highest_discount_string = "") { |
| fake_updater_expected_data_ = fake_updater_expected_data; |
| fake_updater_has_discounts_ = has_discounts; |
| fake_updater_highest_discount_string_ = |
| std::string(fake_updater_highest_discount_string); |
| } |
| |
| void SetExpectedTester(bool is_tester) { expected_tester_ = is_tester; } |
| |
| private: |
| Profile* profile_; |
| std::vector<CartDB::KeyAndValue> fake_loader_data_; |
| cart_db::ChromeCartContentProto fake_updater_expected_data_; |
| bool fake_updater_has_discounts_; |
| std::string fake_updater_highest_discount_string_; |
| bool expected_tester_; |
| }; |
| |
| class FetchDiscountWorkerTest : public testing::Test { |
| public: |
| FetchDiscountWorkerTest() { |
| features_.InitAndEnableFeatureWithParameters( |
| ntp_features::kNtpChromeCartModule, |
| {{ntp_features::kNtpChromeCartModuleAbandonedCartDiscountParam, |
| "true"}}); |
| } |
| void SetUp() override { |
| test_shared_url_loader_factory_ = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory_); |
| |
| mock_fetcher_ = std::make_unique<MockCartDiscountFetcher>(); |
| |
| fake_cart_loader_and_updater_factory_ = |
| std::make_unique<FakeCartLoaderAndUpdaterFactory>(&profile_); |
| } |
| |
| void TearDown() override { |
| FakeCartDiscountFetcher::ClearFetchCount(); |
| is_signin_and_sync_ = false; |
| } |
| |
| // This method transfers mock_fetcher_ ownership. Set all expectations for |
| // mock_fetcher_ before calling this method. |
| void CreateCartDiscountFetcherFactory( |
| CartDiscountFetcher::CartDiscountMap fetcher_fake_result, |
| bool is_tester) { |
| auto factory = std::make_unique<FakeCartDiscountFetcherFactory>(); |
| factory->SetFetcherFakeResult(std::move(fetcher_fake_result)); |
| factory->SetExpectedTester(is_tester); |
| fake_cart_discount_fetcher_factory_ = std::move(factory); |
| } |
| |
| // This method transfers ownership of the following objects, please set all |
| // expectations before calling this method. |
| // * fake_cart_discount_fetcher_factory_ |
| // * fake_cart_loader_and_updater_factory_ |
| void CreateWorker() { |
| fetch_discount_worker_ = std::make_unique<FetchDiscountWorker>( |
| std::move(test_shared_url_loader_factory_), |
| std::move(fake_cart_discount_fetcher_factory_), |
| std::move(fake_cart_loader_and_updater_factory_), |
| is_signin_and_sync_ ? identity_test_env_.identity_manager() : nullptr); |
| } |
| |
| void SignInAndSync() { |
| identity_test_env_.MakePrimaryAccountAvailable(kEmail, |
| signin::ConsentLevel::kSync); |
| identity_test_env_.SetAutomaticIssueOfAccessTokens(true); |
| is_signin_and_sync_ = true; |
| } |
| |
| void CreateFakeFetchedResult() {} |
| |
| 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_; |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| signin::IdentityTestEnvironment identity_test_env_; |
| |
| network::TestURLLoaderFactory test_url_loader_factory_; |
| scoped_refptr<network::SharedURLLoaderFactory> |
| test_shared_url_loader_factory_; |
| std::unique_ptr<FetchDiscountWorker> fetch_discount_worker_; |
| std::unique_ptr<CartDiscountFetcherFactory> |
| fake_cart_discount_fetcher_factory_; |
| |
| std::unique_ptr<MockCartDiscountFetcher> mock_fetcher_; |
| |
| std::unique_ptr<FakeCartLoaderAndUpdaterFactory> |
| fake_cart_loader_and_updater_factory_; |
| |
| TestingProfile profile_; |
| bool is_signin_and_sync_ = false; |
| }; |
| |
| |
| TEST_F(FetchDiscountWorkerTest, TestStart_EndToEnd) { |
| CartDiscountFetcher::CartDiscountMap fake_result; |
| CreateCartDiscountFetcherFactory(std::move(fake_result), false); |
| CreateWorker(); |
| |
| fetch_discount_worker_->Start(base::TimeDelta::FromMilliseconds(0)); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(FetchDiscountWorkerTest, TestStart_DiscountUpdatedWithDiscount) { |
| CartDiscountFetcher::CartDiscountMap fake_result; |
| fake_result.emplace( |
| kMockMerchantACartUrl, |
| MerchantIdAndDiscounts(kMockMerchantAId, kMockMerchantADiscounts, |
| kMockMerchantAHighestPercentOff)); |
| CreateCartDiscountFetcherFactory(std::move(fake_result), false); |
| |
| CartDB::KeyAndValue mockMerchantACartContentKeyAndProto = |
| std::make_pair(kMockMerchantA, kMockMerchantACartContentProto); |
| std::vector<CartDB::KeyAndValue> loader_fake_data( |
| 1, mockMerchantACartContentKeyAndProto); |
| fake_cart_loader_and_updater_factory_->setCartLoaderFakeData( |
| loader_fake_data); |
| |
| cart_db::ChromeCartContentProto cart_content_proto = BuildCartContentProto( |
| kMockMerchantA, kMockMerchantACartUrl, kMockMerchantATimestamp); |
| cart_db::ChromeCartContentProto updater_expected_data = AddDiscountToProto( |
| cart_content_proto, kMockMerchantAId, kMockMerchantADiscounts[0]); |
| fake_cart_loader_and_updater_factory_->setCartDiscountUpdaterExpectedData( |
| updater_expected_data, true, kMockMerchantAHighestPercentOff); |
| |
| CreateWorker(); |
| |
| fetch_discount_worker_->Start(base::TimeDelta::FromMilliseconds(0)); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(FetchDiscountWorkerTest, TestStart_DiscountUpdatedClearDiscount) { |
| // No discount available. |
| CartDiscountFetcher::CartDiscountMap fake_result; |
| CreateCartDiscountFetcherFactory(std::move(fake_result), false); |
| |
| // Loader fake data contatins discount. |
| cart_db::ChromeCartContentProto cart_content_proto = BuildCartContentProto( |
| kMockMerchantA, kMockMerchantACartUrl, kMockMerchantATimestamp); |
| cart_db::ChromeCartContentProto cart_with_discount = AddDiscountToProto( |
| cart_content_proto, kMockMerchantAId, kMockMerchantADiscounts[0]); |
| CartDB::KeyAndValue mockMerchantACartContentKeyAndProto = |
| std::make_pair(kMockMerchantA, cart_with_discount); |
| std::vector<CartDB::KeyAndValue> loader_fake_data( |
| 1, mockMerchantACartContentKeyAndProto); |
| fake_cart_loader_and_updater_factory_->setCartLoaderFakeData( |
| loader_fake_data); |
| |
| // Updater is expected data without discount. |
| cart_db::ChromeCartContentProto updater_expected_data = BuildCartContentProto( |
| kMockMerchantA, kMockMerchantACartUrl, kMockMerchantATimestamp); |
| fake_cart_loader_and_updater_factory_->setCartDiscountUpdaterExpectedData( |
| updater_expected_data, false); |
| |
| CreateWorker(); |
| |
| fetch_discount_worker_->Start(base::TimeDelta::FromMilliseconds(0)); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(FetchDiscountWorkerTest, TestStart_FetcherRefetched) { |
| CartDiscountFetcher::CartDiscountMap fake_result; |
| fake_result.emplace( |
| kMockMerchantACartUrl, |
| MerchantIdAndDiscounts(kMockMerchantAId, kMockMerchantADiscounts, |
| kMockMerchantAHighestPercentOff)); |
| CreateCartDiscountFetcherFactory(std::move(fake_result), false); |
| |
| CartDB::KeyAndValue mockMerchantACartContentKeyAndProto = |
| std::make_pair(kMockMerchantA, kMockMerchantACartContentProto); |
| std::vector<CartDB::KeyAndValue> loader_fake_data( |
| 1, mockMerchantACartContentKeyAndProto); |
| fake_cart_loader_and_updater_factory_->setCartLoaderFakeData( |
| loader_fake_data); |
| |
| cart_db::ChromeCartContentProto cart_content_proto = BuildCartContentProto( |
| kMockMerchantA, kMockMerchantACartUrl, kMockMerchantATimestamp); |
| cart_db::ChromeCartContentProto updater_expected_data = AddDiscountToProto( |
| cart_content_proto, kMockMerchantAId, kMockMerchantADiscounts[0]); |
| fake_cart_loader_and_updater_factory_->setCartDiscountUpdaterExpectedData( |
| updater_expected_data, true, kMockMerchantAHighestPercentOff); |
| |
| CreateWorker(); |
| |
| fetch_discount_worker_->Start(base::TimeDelta::FromMilliseconds(0)); |
| task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(1)); |
| EXPECT_EQ(1, FakeCartDiscountFetcher::GetFetchCount()); |
| |
| task_environment_.FastForwardBy(base::TimeDelta::FromHours(7)); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(2, FakeCartDiscountFetcher::GetFetchCount()); |
| } |
| |
| TEST_F(FetchDiscountWorkerTest, TestTesterFetch) { |
| SignInAndSync(); |
| bool expected_a_tester = true; |
| CartDiscountFetcher::CartDiscountMap fake_result; |
| fake_result.emplace( |
| kMockMerchantACartUrl, |
| MerchantIdAndDiscounts(kMockMerchantAId, kMockMerchantADiscounts, |
| kMockMerchantAHighestPercentOff)); |
| CreateCartDiscountFetcherFactory(std::move(fake_result), expected_a_tester); |
| |
| CartDB::KeyAndValue mockMerchantACartContentKeyAndProto = |
| std::make_pair(kMockMerchantA, kMockMerchantACartContentProto); |
| std::vector<CartDB::KeyAndValue> loader_fake_data( |
| 1, mockMerchantACartContentKeyAndProto); |
| fake_cart_loader_and_updater_factory_->setCartLoaderFakeData( |
| loader_fake_data); |
| |
| cart_db::ChromeCartContentProto cart_content_proto = BuildCartContentProto( |
| kMockMerchantA, kMockMerchantACartUrl, kMockMerchantATimestamp); |
| cart_db::ChromeCartContentProto updater_expected_data = AddDiscountToProto( |
| cart_content_proto, kMockMerchantAId, kMockMerchantADiscounts[0]); |
| fake_cart_loader_and_updater_factory_->setCartDiscountUpdaterExpectedData( |
| updater_expected_data, true, kMockMerchantAHighestPercentOff); |
| fake_cart_loader_and_updater_factory_->SetExpectedTester(expected_a_tester); |
| |
| CreateWorker(); |
| |
| fetch_discount_worker_->Start(base::TimeDelta::FromMilliseconds(0)); |
| task_environment_.RunUntilIdle(); |
| } |