910e62b5创建于 1月15日历史提交
// Copyright 2023 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/ui/safety_hub/menu_notification.h"

#include <memory>

#include "base/json/values_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/ui/safety_hub/revoked_permissions_service.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_service.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/common/content_settings_constraints.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {
constexpr char kUrl1[] = "https://example1.com:443";
const base::TimeDelta kLifetime = base::Days(60);
const base::Time kPastTime = base::Time::Now() - kLifetime;

// TODO(crbug.com/40267370): Use a mock result instead.
std::unique_ptr<RevokedPermissionsResult> CreateRevokedPermissionsResult(
    base::Value::List urls) {
  auto result = std::make_unique<RevokedPermissionsResult>();
  PermissionsData permissions_data;
  for (base::Value& url_val : urls) {
    permissions_data.primary_pattern =
        ContentSettingsPattern::FromString(url_val.GetString());
    permissions_data.permission_types = {ContentSettingsType::GEOLOCATION};
    permissions_data.constraints =
        content_settings::ContentSettingConstraints(kPastTime);
    permissions_data.constraints.set_lifetime(kLifetime);
    result->AddRevokedPermission(permissions_data);
  }
  return result;
}
}  // namespace

class SafetyHubMenuNotificationTest : public testing::Test {
 public:
  SafetyHubMenuNotificationTest() {
    testing_profile_manager_ = std::make_unique<TestingProfileManager>(
        TestingBrowserProcess::GetGlobal());
    EXPECT_TRUE(testing_profile_manager_->SetUp());
    profile_ =
        testing_profile_manager_->CreateTestingProfile("user@example.com");
    EXPECT_TRUE(profile_);
  }
  ~SafetyHubMenuNotificationTest() override = default;

  void SetUp() override {
    // Content settings registry needs to be reset to ensure that it has loaded
    // the correct permission types.
    auto* registry = content_settings::ContentSettingsRegistry::GetInstance();
    registry->ResetForTest();
    service_ = std::make_unique<RevokedPermissionsService>(
        profile(), profile()->GetPrefs());
  }

 protected:
  void FastForwardBy(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

  RevokedPermissionsService* service() { return service_.get(); }
  TestingProfile* profile() { return profile_.get(); }

 private:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<TestingProfileManager> testing_profile_manager_;
  raw_ptr<TestingProfile> profile_ = nullptr;
  std::unique_ptr<RevokedPermissionsService> service_;
};

// TODO(crbug.com/364523673): This test is flaking on android pie builder.
TEST_F(SafetyHubMenuNotificationTest, DISABLED_ToFromDictValue) {
  // Creating a mock menu notification.
  base::Time last = kPastTime + base::Days(30);
  auto notification = std::make_unique<SafetyHubMenuNotification>(
      safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS);
  notification->is_currently_active_ = true;
  notification->impression_count_ = 42;
  notification->first_impression_time_ = kPastTime;
  notification->last_impression_time_ = last;
  notification->current_result_ =
      CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1));

  // When transforming the notification to a Dict, the properties of the
  // notification should be correct.
  base::Value::Dict dict = notification->ToDictValue();
  EXPECT_TRUE(
      dict.FindBool(safety_hub::kSafetyHubMenuNotificationActiveKey).value());
  EXPECT_EQ(42, dict.FindInt(
                    safety_hub::kSafetyHubMenuNotificationImpressionCountKey));
  EXPECT_EQ(
      base::TimeToValue(kPastTime),
      *dict.Find(safety_hub::kSafetyHubMenuNotificationFirstImpressionKey));
  EXPECT_EQ(
      base::TimeToValue(last),
      *dict.Find(safety_hub::kSafetyHubMenuNotificationLastImpressionKey));
  EXPECT_TRUE(dict.contains(safety_hub::kSafetyHubMenuNotificationResultKey));
  // The properties of the contained result should also be correct.
  auto* result_dict =
      dict.FindDict(safety_hub::kSafetyHubMenuNotificationResultKey);
  EXPECT_TRUE(result_dict->contains(kRevokedPermissionsResultKey));
  EXPECT_EQ(1U, result_dict->FindList(kRevokedPermissionsResultKey)->size());
  base::Value::List& revoked_origins =
      *result_dict->FindList(kRevokedPermissionsResultKey);
  EXPECT_EQ(kUrl1, revoked_origins.front());

  // Using the dict from before, we can create another menu notification object
  // that should have the same properties as when it was initially created.
  auto new_notification = std::make_unique<SafetyHubMenuNotification>(
      std::move(dict),
      safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS);
  EXPECT_TRUE(new_notification->is_currently_active_);
  EXPECT_EQ(42, new_notification->impression_count_);
  EXPECT_EQ(kPastTime, new_notification->first_impression_time_);
  EXPECT_EQ(last, new_notification->last_impression_time_);
  EXPECT_FALSE(new_notification->prev_stored_result_.empty());
  EXPECT_EQ(kUrl1, new_notification->prev_stored_result_
                       .FindList(kRevokedPermissionsResultKey)
                       ->front()
                       .GetString());
}

// TODO(crbug.com/364523673): This test is flaking on android pie builder.
TEST_F(SafetyHubMenuNotificationTest, DISABLED_ShouldBeShown) {
  base::TimeDelta interval = base::Days(30);
  auto notification = std::make_unique<SafetyHubMenuNotification>(
      safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS);

  // No result associated with notification, so should not be shown.
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // The notification is updated with a new result that is a trigger. The
  // notification has never been shown, so should be shown.
  std::unique_ptr<RevokedPermissionsResult> result =
      CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1));
  notification->UpdateResult(std::move(result));
  ASSERT_TRUE(notification->ShouldBeShown(interval));

  // Showing the notification once should make it still be shown after.
  notification->Show();
  ASSERT_TRUE(notification->ShouldBeShown(interval));

  // Showing the notification the minimum number of times, but not the minimum
  // period of time, should make it still be shown.
  for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) {
    notification->Show();
    FastForwardBy(base::Hours(1));
  }
  ASSERT_TRUE(notification->ShouldBeShown(interval));

  // Moving past the minimum duration should now make the notification no longer
  // be shown.
  FastForwardBy(kSafetyHubMenuNotificationMinNotificationDuration);
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // The notification can be dismissed now, and it should still not be shown.
  notification->Dismiss();
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // If the result does not change, the notification should not be shown.
  FastForwardBy(interval);
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // When updating to a similar result, the notification should still not be
  // shown.
  std::unique_ptr<RevokedPermissionsResult> similar_result =
      CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1));
  notification->UpdateResult(std::move(similar_result));
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // When updating to a new result, the notification should be shown.
  std::unique_ptr<RevokedPermissionsResult> new_result =
      CreateRevokedPermissionsResult(
          base::Value::List().Append("https://other.com:443"));
  notification->UpdateResult(std::move(new_result));
  ASSERT_TRUE(notification->ShouldBeShown(interval));

  // Notification should still be shown after showing once.
  notification->Show();
  ASSERT_TRUE(notification->ShouldBeShown(interval));

  // Dimissing an active notification should make it not be shown. Note: showing
  // notification once is a prerequisite for dismissing it.
  notification->Dismiss();
  ASSERT_FALSE(notification->ShouldBeShown(interval));

  // Create new notification and new associated result to test passing time but
  // not the count.
  result = CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1));
  auto other_notification = std::make_unique<SafetyHubMenuNotification>(
      safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS);
  other_notification->UpdateResult(std::move(result));
  other_notification->Show();

  // Go past the minimum duration that the notification should be shown. It
  // should still be shown because it was only shown once.
  FastForwardBy(kSafetyHubMenuNotificationMinNotificationDuration +
                base::Days(2));
  ASSERT_TRUE(other_notification->ShouldBeShown(interval));

  // The notification should no longer be shown if it has already been shown a
  // sufficient number of times.
  for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) {
    other_notification->Show();
    FastForwardBy(base::Hours(1));
  }
  ASSERT_FALSE(other_notification->ShouldBeShown(interval));
}

// TODO(crbug.com/364523673): This test is flaking on android pie builder.
TEST_F(SafetyHubMenuNotificationTest, DISABLED_IsCurrentlyActive) {
  auto notification = std::make_unique<SafetyHubMenuNotification>(
      safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS);

  // A notification is not active when it new or when it has not been shown yet.
  ASSERT_FALSE(notification->IsCurrentlyActive());
  std::unique_ptr<RevokedPermissionsResult> result =
      CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1));
  notification->UpdateResult(std::move(result));
  ASSERT_FALSE(notification->IsCurrentlyActive());

  // Showing a notification makes it active.
  notification->Show();
  ASSERT_TRUE(notification->IsCurrentlyActive());

  // Dismissing the nofication makes it not active any more.
  notification->Dismiss();
  ASSERT_FALSE(notification->IsCurrentlyActive());
}

// TODO(crbug.com/40267370): Add tests for other types of Safety Hub services
// and Safety Hub results.