chromium/ios/chrome/browser/enterprise/model/idle/action_runner_impl_unittest.mm

// 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.

#import "ios/chrome/browser/enterprise/model/idle/action_runner_impl.h"

#import "base/test/gmock_callback_support.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/mock_callback.h"
#import "base/time/time.h"
#import "components/enterprise/idle/idle_pref_names.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/chrome/test/ios_chrome_scoped_testing_local_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "testing/gmock/include/gmock/gmock.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"

using ::testing::_;

namespace enterprise_idle {

namespace {

class FakeActionFactory : public ActionFactory {
 public:
  FakeActionFactory() = default;
  ~FakeActionFactory() override = default;

  ActionQueue Build(
      const std::vector<ActionType>& action_types,
      BrowsingDataRemover* main_browsing_data_remover,
      BrowsingDataRemover* incognito_browsing_data_remover) override {
    ActionQueue actions;
    for (ActionType action_type : action_types) {
      auto it = associations_.find(action_type);
      if (it != associations_.end()) {
        actions.push(std::move(it->second));
        associations_.erase(it);
      }
    }
    return actions;
  }

  void Associate(ActionType action_type, std::unique_ptr<Action> action) {
    associations_[action_type] = std::move(action);
  }

 private:
  std::map<ActionType, std::unique_ptr<Action>> associations_;
};

class MockAction : public Action {
 public:
  explicit MockAction(ActionType action_type)
      : Action(static_cast<int>(action_type)) {}

  MOCK_METHOD2(Run, void(ChromeBrowserState*, Continuation));
};

// testing::InvokeArgument<N> does not work with base::OnceCallback, so we
// define our own gMock action to run the 2nd argument.
// Used to mock success and failure in successive actions.
ACTION_P(RunContinuation, success) {
  std::move(const_cast<Action::Continuation&>(arg1)).Run(success);
}

}  // namespace

class IdleActionRunnerTest : public PlatformTest {
 protected:
  void SetUp() override {
    TestChromeBrowserState::Builder test_cbs_builder;
    browser_state_ = std::move(test_cbs_builder).Build();
  }

  void TearDown() override {
    base::RunLoop run_loop;
    run_loop.RunUntilIdle();
    browser_state_.reset();
  }

  void SetIdleTimeoutActions(std::vector<ActionType> action_types) {
    base::Value::List actions;
    for (auto action_type : action_types) {
      actions.Append(static_cast<int>(action_type));
    }
    browser_state()->GetPrefs()->SetList(prefs::kIdleTimeoutActions,
                                         std::move(actions));
  }

  TestChromeBrowserState* browser_state() { return browser_state_.get(); }

 protected:
  web::WebTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  std::unique_ptr<TestChromeBrowserState> browser_state_;
};

// Tests that the order of actions in the pref doesn't matter. They still run
// by order of priority.
TEST_F(IdleActionRunnerTest, PrefOrderDoesNotMatter) {
  std::unique_ptr<FakeActionFactory> action_factory =
      std::make_unique<FakeActionFactory>();
  base::MockCallback<ActionRunner::ActionsCompletedCallback>
      actions_completed_callback;
  ActionRunnerImpl runner(browser_state());
  SetIdleTimeoutActions({ActionType::kCloseTabs,
                         ActionType::kClearBrowsingHistory,
                         ActionType::kSignOut});

  auto close_tabs = std::make_unique<MockAction>(ActionType::kCloseTabs);
  auto clear_history =
      std::make_unique<MockAction>(ActionType::kClearBrowsingHistory);
  auto sign_out = std::make_unique<MockAction>(ActionType::kSignOut);

  testing::InSequence in_sequence;
  EXPECT_CALL(*clear_history, Run(browser_state(), _))
      .WillOnce(RunContinuation(true));
  EXPECT_CALL(*close_tabs, Run(browser_state(), _))
      .WillOnce(RunContinuation(true));
  EXPECT_CALL(*sign_out, Run(browser_state(), _))
      .WillOnce(RunContinuation(true));

  action_factory->Associate(ActionType::kCloseTabs, std::move(close_tabs));
  action_factory->Associate(ActionType::kClearBrowsingHistory,
                            std::move(clear_history));
  action_factory->Associate(ActionType::kSignOut, std::move(sign_out));

  EXPECT_CALL(actions_completed_callback, Run()).Times(1);
  runner.SetActionFactoryForTesting(std::move(action_factory));
  runner.Run(actions_completed_callback.Get());
}

// Tests that when a higher-priority action fails, the lower-priority actions
// don't run.
TEST_F(IdleActionRunnerTest, OtherActionsDontRunOnFailure) {
  std::unique_ptr<base::HistogramTester> histogram_tester =
      std::make_unique<base::HistogramTester>();
  std::unique_ptr<FakeActionFactory> action_factory =
      std::make_unique<FakeActionFactory>();
  ActionRunnerImpl runner(browser_state());
  base::MockCallback<ActionRunner::ActionsCompletedCallback>
      actions_completed_callback;
  SetIdleTimeoutActions({ActionType::kCloseTabs, ActionType::kSignOut});

  auto close_tabs = std::make_unique<MockAction>(ActionType::kCloseTabs);
  auto sign_out = std::make_unique<MockAction>(ActionType::kSignOut);

  // "sign_out" shouldn't run, because "close_tabs" fails.
  testing::InSequence in_sequence;
  EXPECT_CALL(*close_tabs, Run(browser_state(), _))
      .WillOnce(RunContinuation(false));
  EXPECT_CALL(*sign_out, Run(_, _)).Times(0);

  action_factory->Associate(ActionType::kCloseTabs, std::move(close_tabs));
  action_factory->Associate(ActionType::kSignOut, std::move(sign_out));

  EXPECT_CALL(actions_completed_callback, Run()).Times(0);
  runner.SetActionFactoryForTesting(std::move(action_factory));
  runner.Run(actions_completed_callback.Get());
  histogram_tester->ExpectUniqueSample(
      "Enterprise.IdleTimeoutPolicies.Success.AllActions", false, 1);
}

// Tests that it does nothing when the "IdleTimeoutActions" pref is empty.
TEST_F(IdleActionRunnerTest, DoNothingWithEmptyPref) {
  std::unique_ptr<FakeActionFactory> action_factory =
      std::make_unique<FakeActionFactory>();
  ActionRunnerImpl runner(browser_state());
  base::MockCallback<ActionRunner::ActionsCompletedCallback>
      actions_completed_callback;

  // "IdleTimeoutActions" is deliberately unset.
  auto clear_browsing_history =
      std::make_unique<MockAction>(ActionType::kClearBrowsingHistory);
  auto clear_cookies_and_site_data =
      std::make_unique<MockAction>(ActionType::kClearCookiesAndOtherSiteData);

  EXPECT_CALL(*clear_browsing_history, Run(_, _)).Times(0);
  EXPECT_CALL(*clear_cookies_and_site_data, Run(_, _)).Times(0);

  action_factory->Associate(ActionType::kClearBrowsingHistory,
                            std::move(clear_browsing_history));
  action_factory->Associate(ActionType::kClearCookiesAndOtherSiteData,
                            std::move(clear_cookies_and_site_data));

  EXPECT_CALL(actions_completed_callback, Run()).Times(0);
  runner.SetActionFactoryForTesting(std::move(action_factory));
  runner.Run(actions_completed_callback.Get());
}

}  // namespace enterprise_idle