chromium/ios/chrome/browser/default_browser/model/default_browser_promo_event_exporter_unittest.mm

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

#import "ios/chrome/browser/default_browser/model/default_browser_promo_event_exporter.h"

#import "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/feature_engagement/test/test_tracker.h"
#import "ios/chrome/browser/default_browser/model/default_browser_interest_signals.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/default_browser/model/utils_test_support.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/platform_test.h"

namespace {
constexpr base::TimeDelta kMoreThan3Day = base::Days(3) + base::Minutes(1);
constexpr base::TimeDelta kMoreThan30Days = base::Days(30) + base::Minutes(1);
}  // namespace

class DefaultBrowserEventExporterTest : public PlatformTest {
 public:
  DefaultBrowserEventExporterTest() {}
  ~DefaultBrowserEventExporterTest() override {}
  base::RepeatingCallback<void(bool)> BoolArgumentQuitClosure() {
    return base::IgnoreArgs<bool>(run_loop_.QuitClosure());
  }

 protected:
  void SetUp() override {
    PlatformTest::SetUp();
    ClearDefaultBrowserPromoData();
  }
  void TearDown() override {
    ClearDefaultBrowserPromoData();
    PlatformTest::TearDown();
  }

  void RequestExportEventsAndVerifyCallback() {
    __block bool callback_called = false;
    feature_engagement::TrackerEventExporter::ExportEventsCallback callback =
        base::BindOnce(
            ^(const std::vector<
                feature_engagement::TrackerEventExporter::EventData> events) {
              export_events_ = events;
              callback_called = true;
            });

    DefaultBrowserEventExporter* exporter = new DefaultBrowserEventExporter();
    exporter->ExportEvents(std::move(callback));

    EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
        base::test::ios::kWaitForActionTimeout, ^bool() {
          base::RunLoop().RunUntilIdle();
          return callback_called;
        }));
  }

  int GetExportEventsCount() { return export_events_.size(); }

  // Initializes the feature engagement tracker  with the default browser
  // exporter and set basic common conditions for default browser promos.
  void InitTrackerAndSetBasicConditions() {
    // Initialize tracker with the default browser exporter.
    tracker_ = feature_engagement::CreateTestTracker(
        std::make_unique<DefaultBrowserEventExporter>());

    // Make sure tracker is initialized.
    tracker_->AddOnInitializedCallback(BoolArgumentQuitClosure());
    run_loop_.Run();

    // Promos can be displayed only after Chrome opened 7 times.
    SatisfyChromeOpenCondition();
  }

  void SatisfyChromeOpenCondition() {
    // Promos can be displayed only after Chrome opened 7 times.
    for (int i = 0; i < 7; i++) {
      tracker_->NotifyEvent(feature_engagement::events::kChromeOpened);
    }
  }

  web::WebTaskEnvironment task_environment_;
  base::RunLoop run_loop_;
  std::unique_ptr<feature_engagement::Tracker> tracker_;
  std::vector<feature_engagement::TrackerEventExporter::EventData>
      export_events_;
};

TEST_F(DefaultBrowserEventExporterTest, TestFRETimestampMigration) {
  // No events to export.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // When there is a FRE event, it should be exported.
  ClearDefaultBrowserPromoData();
  LogUserInteractionWithFirstRunPromo();

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 1);

  // Second time there shouldn't be any events to export.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // When FRE happened after migration, there shouldn't be any events to export.
  ClearDefaultBrowserPromoData();
  default_browser::NotifyDefaultBrowserFREPromoShown(nullptr);

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);
}

TEST_F(DefaultBrowserEventExporterTest, TestPromoInterestEventsMigration) {
  // No events to export.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // Check when there is only 1 event.
  ClearDefaultBrowserPromoData();
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 1);

  // Check that exporting second time will not have any events.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // Check when there are 2 events for the same promo type
  ClearDefaultBrowserPromoData();
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 2);

  // Check when there are events for all 4 promo types.
  ClearDefaultBrowserPromoData();
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeMadeForIOS);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeStaySafe);

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 4);
}

TEST_F(DefaultBrowserEventExporterTest, TestPromoImpressionsMigration) {
  // No events to export.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // Check when there is 1 generic promo, it should create 1 event to export.
  ClearDefaultBrowserPromoData();
  LogUserInteractionWithFullscreenPromo();

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 1);

  // Check that exporting second time will not have any events.
  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 0);

  // Check when there is 1 tailored promo it should create 4 events to export.
  ClearDefaultBrowserPromoData();
  LogUserInteractionWithTailoredFullscreenPromo();

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 4);

  // Check when there is 1 generic and 1 tailored promo, should create 5 events
  // to export.
  ClearDefaultBrowserPromoData();
  LogUserInteractionWithFullscreenPromo();
  LogUserInteractionWithTailoredFullscreenPromo();

  RequestExportEventsAndVerifyCallback();
  EXPECT_EQ(GetExportEventsCount(), 5);
}

// Checks that none of the promos triggers when there are no events exported.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationNoEvents) {
  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // None of the promos should trigger.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that none of the promos triggers even when conditions are met when
// cooldown from FRE is not satisfied.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationFRECooldown) {
  // Write to user defaults before creating the tracker.
  LogUserInteractionWithFirstRunPromo();
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeMadeForIOS);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeStaySafe);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // None of the promos should trigger.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that none of the promos triggers even when conditions are not met but
// cooldown from FRE is ok.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationConditionsNotMet) {
  // Write to user defaults before creating the tracker.
  SimulateUserInteractionWithPromos(kMoreThan3Day, /*interectedWithFRE=*/true,
                                    /*genericCount=*/0, /*tailoredCount=*/0,
                                    /*totalCount=*/1);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // None of the promos should trigger.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that promos trigger when conditions are met and
// cooldown from FRE is ok.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationConditionsMet) {
  // Write to user defaults before creating the tracker.
  SimulateUserInteractionWithPromos(kMoreThan3Day, /*interectedWithFRE=*/true,
                                    /*genericCount=*/0, /*tailoredCount=*/0,
                                    /*totalCount=*/1);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeMadeForIOS);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeStaySafe);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // All promos would trigger.
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that generic promo doesn't tigger second time.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationSecondGenericPromo) {
  // Write to user defaults before creating the tracker.
  SimulateUserInteractionWithPromos(kMoreThan30Days, /*interectedWithFRE=*/true,
                                    /*genericCount=*/1, /*tailoredCount=*/0,
                                    /*totalCount=*/2);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeMadeForIOS);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeStaySafe);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // Tailored will trigger but generic will not.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that tailored promos don't trigger second time.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationSecondTailoredPromo) {
  // Write to user defaults before creating the tracker.
  SimulateUserInteractionWithPromos(kMoreThan30Days, /*interectedWithFRE=*/true,
                                    /*genericCount=*/0, /*tailoredCount=*/1,
                                    /*totalCount=*/2);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeMadeForIOS);
  LogLikelyInterestedDefaultBrowserUserActivity(DefaultPromoTypeStaySafe);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // Generic will trigger but tailored will not.
  EXPECT_TRUE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoMadeForIOSFeature));
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoStaySafeFeature));
}

// Checks that generic promo doesn't trigger when conditions are met but are too
// old.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationGenericOldCondition) {
  // Write to user defaults before creating the tracker.
  SimulateUserInterestedDefaultBrowserUserActivity(DefaultPromoTypeGeneral,
                                                   kMoreThan30Days);
  SimulateUserInteractionWithPromos(kMoreThan30Days, /*interectedWithFRE=*/true,
                                    /*genericCount=*/0, /*tailoredCount=*/0,
                                    /*totalCount=*/1);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // Generic promo should not trigger.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoGenericDefaultBrowserFeature));
}

// Checks that tailored promo doesn't trigger when conditions are met but are
// too old.
TEST_F(DefaultBrowserEventExporterTest, TestMigrationTailoredOldCondition) {
  // Write to user defaults before creating the tracker.
  SimulateUserInterestedDefaultBrowserUserActivity(DefaultPromoTypeAllTabs,
                                                   kMoreThan30Days);
  SimulateUserInteractionWithPromos(kMoreThan30Days, /*interectedWithFRE=*/true,
                                    /*genericCount=*/0, /*tailoredCount=*/0,
                                    /*totalCount=*/1);

  // Initialize tracker.
  InitTrackerAndSetBasicConditions();

  // All tabs promo should not trigger.
  EXPECT_FALSE(tracker_->WouldTriggerHelpUI(
      feature_engagement::kIPHiOSPromoAllTabsFeature));
}