chromium/chrome/browser/commerce/merchant_viewer/merchant_viewer_data_manager_unittest.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/commerce/merchant_viewer/merchant_viewer_data_manager.h"

#include <optional>

#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/histogram_macros_local.h"
#include "base/ranges/algorithm.h"
#include "base/test/metrics/histogram_tester.h"
#include "chrome/browser/commerce/merchant_viewer/merchant_viewer_data_manager_factory.h"
#include "chrome/browser/persisted_state_db/session_proto_db_factory.h"
#include "chrome/test/base/testing_profile.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/session_proto_db/session_proto_db.h"
#include "content/public/browser/android/browser_context_handle.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest-spi.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using std::string;
using std::vector;

namespace {
merchant_signal_db::MerchantSignalContentProto BuildProto(
    const char* hostname,
    base::Time time_created) {
  merchant_signal_db::MerchantSignalContentProto proto;
  proto.set_key(hostname);
  proto.set_trust_signals_message_displayed_timestamp(
      time_created.InSecondsFSinceUnixEpoch());
  return proto;
}
}  // namespace

class MerchantViewerDataManagerTest : public testing::Test {
 public:
  MerchantViewerDataManagerTest()
      : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}

  void SetUp() override {
    testing::Test::SetUp();

    service_ = MerchantViewerDataManagerFactory::GetForProfile(&profile_);
  }

  void TearDown() override {
    if (service_)
      service_->GetDB()->Destroy();
  }

  void OperationEvaluation(base::OnceClosure closure,
                           bool expected_success,
                           bool actual_success) {
    EXPECT_EQ(expected_success, actual_success);
    std::move(closure).Run();
  }

  void LoadEntriesEvaluation(base::OnceClosure closure,
                             vector<string> expected_hostnames,
                             bool success,
                             MerchantViewerDataManager::MerchantSignals found) {
    EXPECT_TRUE(success);

    EXPECT_THAT(base::ToVector(
                    found, [](const auto& item) { return item.second.key(); }),
                testing::UnorderedElementsAreArray(expected_hostnames));

    std::move(closure).Run();
  }

 protected:
  base::test::ScopedFeatureList features_;
  // Required to run tests from UI thread.
  content::BrowserTaskEnvironment task_environment_;
  TestingProfile profile_;
  raw_ptr<MerchantViewerDataManager> service_;
};

const char kMockMerchantA[] = "foo.com";
const char kMockMerchantUrlA[] = "https://foo.com/";
const char kMockMerchantB[] = "bar.com";
const char kMockMerchantUrlB[] = "https://bar.com/";

TEST_F(MerchantViewerDataManagerTest, TestDeleteMerchantViewerDataForOrigins) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, base::Time::Now());

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, base::Time::Now());

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();

  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();

  vector<string> hostnames_before_deletion = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[2].QuitClosure(),
                     hostnames_before_deletion));
  run_loop[2].Run();

  base::flat_set<GURL> deleted_origins = {GURL(kMockMerchantUrlB)};

  service_->DeleteMerchantViewerDataForOrigins(deleted_origins);
  task_environment_.RunUntilIdle();

  vector<string> hostnames_after_deletion = {kMockMerchantA};
  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForOrigins", 1, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       TestDeleteMerchantViewerDataForOriginsEmpty) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, base::Time::Now());

  base::RunLoop run_loop[3];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();
  vector<string> expected_hostnames = {kMockMerchantA};

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[1].QuitClosure(), expected_hostnames));
  run_loop[1].Run();

  base::flat_set<GURL> deleted_origins = {};

  service_->DeleteMerchantViewerDataForOrigins(deleted_origins);
  task_environment_.RunUntilIdle();

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[2].QuitClosure(), expected_hostnames));
  run_loop[2].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForOrigins", 0, 1);
}

TEST_F(MerchantViewerDataManagerTest, DeleteMerchantViewerDataForTimeRange) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  base::Time start_time = base::Time::Now();
  base::Time end_time = start_time + base::Days(3);

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, start_time + base::Days(1));

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, start_time + base::Days(2));

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();

  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();

  vector<string> hostnames_before_deletion = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[2].QuitClosure(),
                     hostnames_before_deletion));
  run_loop[2].Run();

  service_->DeleteMerchantViewerDataForTimeRange(start_time, end_time);
  task_environment_.RunUntilIdle();
  vector<string> hostnames_after_deletion = {};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForTimeRange", 2, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForTimeRangeNoDeletion) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  base::Time start_time = base::Time::Now();
  base::Time end_time = start_time + base::Days(3);

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, start_time - base::Days(1));

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, start_time - base::Days(1));

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();

  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();
  vector<string> expected_hostnames = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[2].QuitClosure(), expected_hostnames));
  run_loop[2].Run();

  service_->DeleteMerchantViewerDataForTimeRange(start_time, end_time);
  task_environment_.RunUntilIdle();

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[3].QuitClosure(), expected_hostnames));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForTimeRange", 0, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForTimeRangeWithinWindow) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  base::Time start_time = base::Time::Now();
  base::Time end_time = start_time + base::Days(3);

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, start_time - base::Days(1));

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, start_time + base::Days(1));

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();

  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();
  vector<string> hostnames_before_deletion = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[2].QuitClosure(),
                     hostnames_before_deletion));
  run_loop[2].Run();

  service_->DeleteMerchantViewerDataForTimeRange(start_time, end_time);
  task_environment_.RunUntilIdle();
  vector<string> hostnames_after_deletion = {kMockMerchantA};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForTimeRange", 1, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForOrigins_OriginNotFound) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  base::RunLoop run_loop[1];
  base::flat_set<GURL> deleted_origins = {GURL(kMockMerchantUrlB)};
  service_->DeleteMerchantViewerDataForOrigins(std::move(deleted_origins));
  task_environment_.RunUntilIdle();
  vector<string> expected_hostnames = {};

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[0].QuitClosure(), expected_hostnames));
  run_loop[0].Run();
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForOrigins_VerifyCount) {
  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, base::Time::Now());

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, base::Time::Now());

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();
  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();
  vector<string> expected_hostnames = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[2].QuitClosure(), expected_hostnames));
  run_loop[2].Run();

  base::flat_set<GURL> deleted_origins = {GURL(kMockMerchantUrlA),
                                          GURL(kMockMerchantUrlB)};

  service_->DeleteMerchantViewerDataForOrigins(deleted_origins);
  task_environment_.RunUntilIdle();
  vector<string> expected_hostnames_after_deletion = {};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     expected_hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.DeleteMerchantViewerDataForOrigins", 2, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForOrigins_ForceClearAll) {
  features_.InitAndEnableFeatureWithParameters(
      commerce::kCommerceMerchantViewer,
      {{"delete_all_merchants_on_clear_history", "true"}});

  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, base::Time::Now());

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, base::Time::Now());

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();
  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();
  vector<string> expected_hostnames = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(base::BindOnce(
      &MerchantViewerDataManagerTest::LoadEntriesEvaluation,
      base::Unretained(this), run_loop[2].QuitClosure(), expected_hostnames));
  run_loop[2].Run();

  base::flat_set<GURL> deleted_origins = {GURL(kMockMerchantUrlA)};

  service_->DeleteMerchantViewerDataForOrigins(deleted_origins);
  task_environment_.RunUntilIdle();
  vector<string> expected_hostnames_after_deletion = {};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     expected_hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.ForceClearMerchantsForOrigins", true, 1);
}

TEST_F(MerchantViewerDataManagerTest,
       DeleteMerchantViewerDataForTimeRange_ForceAll) {
  features_.InitAndEnableFeatureWithParameters(
      commerce::kCommerceMerchantViewer,
      {{"delete_all_merchants_on_clear_history", "true"}});

  base::HistogramTester histogram_tester;

  SessionProtoDB<merchant_signal_db::MerchantSignalContentProto>* db =
      service_->GetDB();

  base::Time start_time = base::Time::Now();
  base::Time end_time = start_time + base::Days(3);

  merchant_signal_db::MerchantSignalContentProto protoA =
      BuildProto(kMockMerchantA, start_time - base::Days(4));

  merchant_signal_db::MerchantSignalContentProto protoB =
      BuildProto(kMockMerchantB, start_time + base::Days(1));

  base::RunLoop run_loop[4];

  db->InsertContent(
      kMockMerchantA, protoA,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[0].QuitClosure(), true));
  run_loop[0].Run();

  db->InsertContent(
      kMockMerchantB, protoB,
      base::BindOnce(&MerchantViewerDataManagerTest::OperationEvaluation,
                     base::Unretained(this), run_loop[1].QuitClosure(), true));
  run_loop[1].Run();
  vector<string> hostnames_before_deletion = {kMockMerchantA, kMockMerchantB};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[2].QuitClosure(),
                     hostnames_before_deletion));
  run_loop[2].Run();

  service_->DeleteMerchantViewerDataForTimeRange(start_time, end_time);
  task_environment_.RunUntilIdle();
  vector<string> hostnames_after_deletion = {};

  db->LoadAllEntries(
      base::BindOnce(&MerchantViewerDataManagerTest::LoadEntriesEvaluation,
                     base::Unretained(this), run_loop[3].QuitClosure(),
                     hostnames_after_deletion));
  run_loop[3].Run();

  histogram_tester.ExpectUniqueSample(
      "MerchantViewer.DataManager.ForceClearMerchantsForTimeRange", true, 1);
}