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

#include "ash/scanner/scanner_controller.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/accelerator_actions.h"
#include "ash/public/cpp/scanner/scanner_delegate.h"
#include "ash/public/cpp/scanner/scanner_enums.h"
#include "ash/public/cpp/scanner/scanner_feedback_info.h"
#include "ash/public/cpp/system/scoped_toast_pause.h"
#include "ash/public/cpp/system/toast_manager.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/scanner/fake_scanner_delegate.h"
#include "ash/scanner/fake_scanner_profile_scoped_delegate.h"
#include "ash/scanner/scanner_action_view_model.h"
#include "ash/scanner/scanner_enterprise_policy.h"
#include "ash/scanner/scanner_metrics.h"
#include "ash/scanner/scanner_session.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/system/toast/toast_overlay.h"
#include "ash/test/ash_test_base.h"
#include "ash/test_shell_delegate.h"
#include "ash/wm/screen_pinning_controller.h"
#include "ash/wm/window_util.h"
#include "base/check.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_refptr.h"
#include "base/strings/string_split.h"
#include "base/strings/string_view_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/protobuf_matchers.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/test/values_test_util.h"
#include "chromeos/ash/components/specialized_features/feature_access_checker.h"
#include "components/account_id/account_id.h"
#include "components/feedback/feedback_constants.h"
#include "components/manta/manta_status.h"
#include "components/manta/proto/scanner.pb.h"
#include "components/manta/scanner_provider.h"
#include "google_apis/gaia/gaia_id.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/message_center/fake_message_center.h"
#include "ui/message_center/message_center.h"
#include "ui/views/view_utils.h"
#include "ui/wm/core/window_util.h"
#include "url/gurl.h"

namespace ash {

using chromeos::AppType;

namespace {

using ::base::test::EqualsProto;
using ::base::test::InvokeFuture;
using ::base::test::IsJson;
using ::base::test::RunOnceCallback;
using ::base::test::ValueIs;
using ::testing::_;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::WithArg;

constexpr char kScannerActionSuccessToastId[] = "scanner_action_success";
constexpr char kScannerActionFailureToastId[] = "scanner_action_failure";

FakeScannerDelegate* GetFakeScannerDelegate(
    ScannerController& scanner_controller) {
  return static_cast<FakeScannerDelegate*>(
      scanner_controller.delegate_for_testing());
}

FakeScannerProfileScopedDelegate* GetFakeScannerProfileScopedDelegate(
    ScannerController& scanner_controller) {
  return static_cast<FakeScannerProfileScopedDelegate*>(
      scanner_controller.delegate_for_testing()->GetProfileScopedDelegate());
}

scoped_refptr<base::RefCountedMemory> MakeJpegBytes(int width = 100,
                                                    int height = 100) {
  gfx::ImageSkia img = gfx::test::CreateImageSkia(width, height);
  std::optional<std::vector<uint8_t>> data =
      gfx::JPEGCodec::Encode(*img.bitmap(), /*quality=*/90);
  CHECK(data.has_value());
  return base::MakeRefCounted<base::RefCountedBytes>(std::move(*data));
}

class MockNewWindowDelegate : public TestNewWindowDelegate {
 public:
  MOCK_METHOD(void,
              OpenUrl,
              (const GURL& url, OpenUrlFrom from, Disposition disposition),
              (override));
};

// Only used in ScannerControllerNoFixtureTest.
class MockToastManager : public ToastManager {
 public:
  MOCK_METHOD(void, Show, (ToastData data), (override));
  MOCK_METHOD(void, Cancel, (std::string_view id), (override));
  MOCK_METHOD(bool,
              RequestFocusOnActiveToastButton,
              (std::string_view id),
              (override));
  MOCK_METHOD(bool, IsToastShown, (std::string_view id), (const override));
  MOCK_METHOD(bool,
              IsToastButtonFocused,
              (std::string_view id),
              (const override));
  MOCK_METHOD(std::unique_ptr<ScopedToastPause>,
              CreateScopedPause,
              (),
              (override));
  MOCK_METHOD(void, Pause, (), (override));
  MOCK_METHOD(void, Resume, (), (override));
};

class MockScannerDelegate : public ScannerDelegate {
 public:
  MOCK_METHOD(ScannerProfileScopedDelegate*,
              GetProfileScopedDelegate,
              (),
              (override));
  MOCK_METHOD(void,
              OpenFeedbackDialog,
              (const AccountId& account_id,
               ScannerFeedbackInfo feedback_info,
               SendFeedbackCallback send_feedback_callback),
              (override));
};

class ScannerControllerTest : public AshTestBase {
 public:
  ScannerControllerTest() = default;
  ScannerControllerTest(const ScannerControllerTest&) = delete;
  ScannerControllerTest& operator=(const ScannerControllerTest&) = delete;
  ~ScannerControllerTest() override = default;

  // AshTestBase:
  void SetUp() override {
    auto shell_delegate = std::make_unique<TestShellDelegate>();
    shell_delegate->SetSendSpecializedFeatureFeedbackCallback(
        mock_send_specialized_feature_feedback_.Get());
    set_shell_delegate(std::move(shell_delegate));
    AshTestBase::SetUp();
  }

  base::MockCallback<TestShellDelegate::SendSpecializedFeatureFeedbackCallback>&
  mock_send_specialized_feature_feedback() {
    return mock_send_specialized_feature_feedback_;
  }

  MockNewWindowDelegate& mock_new_window_delegate() {
    return mock_new_window_delegate_;
  }

 private:
  testing::StrictMock<base::MockCallback<
      TestShellDelegate::SendSpecializedFeatureFeedbackCallback>>
      mock_send_specialized_feature_feedback_;
  MockNewWindowDelegate mock_new_window_delegate_;
  base::test::ScopedFeatureList scoped_feature_list_{features::kScannerUpdate};
};

TEST_F(ScannerControllerTest, CanStartSessionIfFeatureChecksPass) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));

  EXPECT_TRUE(scanner_controller->CanStartSession());
  EXPECT_TRUE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest, CanNotStartSessionIfFeatureChecksFail) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kDisabledInSettings}));

  EXPECT_FALSE(scanner_controller->CanStartSession());
  EXPECT_FALSE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest,
       CanStartSessionIfEnterprisePolicyAllowedWithModelImprovement) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kAllowedWithModelImprovement));

  EXPECT_TRUE(scanner_controller->CanStartSession());
  EXPECT_TRUE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest,
       CanStartSessionIfEnterprisePolicyAllowedWithoutModelImprovement) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(
          ScannerEnterprisePolicy::kAllowedWithoutModelImprovement));

  EXPECT_TRUE(scanner_controller->CanStartSession());
  EXPECT_TRUE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest, CanStartSessionIfEnterprisePolicyIsInvalidValue) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed, 3);

  EXPECT_TRUE(scanner_controller->CanStartSession());
  EXPECT_TRUE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest,
       CannotStartSessionIfDisallowedByEnterprisePolicy) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kDisallowed));

  EXPECT_FALSE(scanner_controller->CanStartSession());
  EXPECT_FALSE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest, CannotStartSessionInPinnedMode) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));

  std::unique_ptr<aura::Window> pinned_window =
      CreateWindowWithAppType(AppType::SYSTEM_APP);
  wm::ActivateWindow(pinned_window.get());
  window_util::PinWindow(pinned_window.get(), /*trusted=*/false);
  ASSERT_TRUE(Shell::Get()->screen_pinning_controller()->IsPinned());

  EXPECT_FALSE(scanner_controller->CanStartSession());
  EXPECT_FALSE(scanner_controller->StartNewSession());
}

TEST_F(ScannerControllerTest, CanShowFeatureSettingsToggleIfNoChecksFail) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));

  EXPECT_TRUE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest, DoesNotShowFeatureSettingsToggleInPinnedMode) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));

  std::unique_ptr<aura::Window> pinned_window =
      CreateWindowWithAppType(AppType::SYSTEM_APP);
  wm::ActivateWindow(pinned_window.get());
  window_util::PinWindow(pinned_window.get(), /*trusted=*/false);
  ASSERT_TRUE(Shell::Get()->screen_pinning_controller()->IsPinned());

  EXPECT_FALSE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest, CanShowUiIfConsentNotAcceptedOnly) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kConsentNotAccepted}));

  EXPECT_TRUE(scanner_controller->CanShowUi());
  EXPECT_TRUE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest,
       CanShowUiIfEnterprisePolicyAllowedWithModelImprovement) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kAllowedWithModelImprovement));

  EXPECT_TRUE(scanner_controller->CanShowUi());
  EXPECT_TRUE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest,
       CanShowUiIfEnterprisePolicyAllowedWithoutModelImprovement) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(
          ScannerEnterprisePolicy::kAllowedWithoutModelImprovement));

  EXPECT_TRUE(scanner_controller->CanShowUi());
  EXPECT_TRUE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest, CanShowUiIfEnterprisePolicyIsInvalidValue) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed, 3);

  EXPECT_TRUE(scanner_controller->CanShowUi());
  EXPECT_TRUE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest, CannotShowUiIfDisallowedByEnterprisePolicy) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kDisallowed));

  EXPECT_FALSE(scanner_controller->CanShowUi());
  EXPECT_FALSE(ScannerController::CanShowUiForShell());
}

TEST(ScannerControllerNoFixtureTest, CanShowUiForShellFalseWhenNoShell) {
  ASSERT_FALSE(Shell::HasInstance());
  EXPECT_FALSE(ScannerController::CanShowUiForShell());
}

class ScannerControllerDisabledTest : public AshTestBase {
 public:
  ScannerControllerDisabledTest() {
    scoped_feature_list_.InitWithFeatures(
        /*enabled_features=*/{}, /*disabled_features=*/{
            features::kScannerUpdate, features::kScannerDogfood});
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(ScannerControllerDisabledTest, CanShowUiForShellFalseWhenNoController) {
  ASSERT_FALSE(Shell::Get()->scanner_controller());
  EXPECT_FALSE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest, CannotShowUiInPinnedMode) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));

  std::unique_ptr<aura::Window> pinned_window =
      CreateWindowWithAppType(AppType::SYSTEM_APP);
  wm::ActivateWindow(pinned_window.get());
  window_util::PinWindow(pinned_window.get(), /*trusted=*/false);
  ASSERT_TRUE(Shell::Get()->screen_pinning_controller()->IsPinned());

  EXPECT_FALSE(scanner_controller->CanShowUi());
  EXPECT_FALSE(ScannerController::CanShowUiForShell());
}

TEST_F(ScannerControllerTest, CanShowUiAfterExitingPinnedMode) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  std::unique_ptr<aura::Window> pinned_window =
      CreateWindowWithAppType(AppType::SYSTEM_APP);
  wm::ActivateWindow(pinned_window.get());
  window_util::PinWindow(pinned_window.get(), /*trusted=*/false);
  ASSERT_TRUE(Shell::Get()->screen_pinning_controller()->IsPinned());
  ASSERT_FALSE(scanner_controller->CanShowUi());
  ASSERT_FALSE(ScannerController::CanShowUiForShell());

  Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
      AcceleratorAction::kUnpin, {});

  EXPECT_TRUE(scanner_controller->CanShowUi());
  EXPECT_TRUE(ScannerController::CanShowUiForShell());
}

TEST(ScannerControllerNoFixtureTest, CanShowUiForShellFalseWhenNoShellMetrics) {
  base::HistogramTester histogram_tester;

  ASSERT_FALSE(Shell::HasInstance());
  ASSERT_FALSE(ScannerController::CanShowUiForShell());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToNoShellInstance, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerDisabledTest,
       CanShowUiForShellFalseWhenNoControllerMetrics) {
  base::HistogramTester histogram_tester;

  ASSERT_FALSE(Shell::Get()->scanner_controller());
  ASSERT_FALSE(ScannerController::CanShowUiForShell());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToNoControllerOnShell,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiForShellFalseWhenPinnedMetrics) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  std::unique_ptr<aura::Window> pinned_window =
      CreateWindowWithAppType(AppType::SYSTEM_APP);
  wm::ActivateWindow(pinned_window.get());
  window_util::PinWindow(pinned_window.get(), /*trusted=*/false);
  ASSERT_TRUE(Shell::Get()->screen_pinning_controller()->IsPinned());
  // This must be after pinning, as `SunfishScannerFeatureWatcher` may
  // automatically call `CanShowUi` when the pinned state changes.
  base::HistogramTester histogram_tester;

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToPinnedMode, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST(ScannerControllerNoFixtureTest,
     CanShowUiFalseWhenNoProfileScopedDelegateMetrics) {
  base::HistogramTester histogram_tester;
  SessionControllerImpl session_controller;
  auto mock_delegate = std::make_unique<MockScannerDelegate>();
  EXPECT_CALL(*mock_delegate, GetProfileScopedDelegate())
      .WillRepeatedly(Return(nullptr));
  ScannerController scanner_controller(std::move(mock_delegate),
                                       session_controller,
                                       /*screen_pinning_controller=*/nullptr);

  ASSERT_FALSE(scanner_controller.CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::
          kCanShowUiReturnedFalseDueToNoProfileScopedDelegate,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest,
       CanShowUiFalseWhenEnterprisePolicyDisallowedMetrics) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kDisallowed));
  // This must be after setting the preference, as some preference observers
  // like `SunfishScannerFeatureWatcher` may automatically call `CanShowUi`.
  base::HistogramTester histogram_tester;

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToEnterprisePolicy, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiFalseWhenSettingsToggleDisabledMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kDisabledInSettings}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToSettingsToggle, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiFalseWhenFeatureFlagDisabledMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kFeatureFlagDisabled}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToFeatureFlag, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest,
       CanShowUiFalseWhenFeatureManagementCheckFailedMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::
              kFeatureManagementCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToFeatureManagement,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiFalseWhenSecretKeyCheckFailedMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kSecretKeyCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToSecretKey, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiFalseWhenCountryCheckFailedMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kCountryCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToCountry, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiFalseWhenKioskModeMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::
              kDisabledInKioskModeCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToKioskMode, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest,
       CanShowUiFalseMultipleFeatureAccessCheckFailsWithoutConsentMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::
              kFeatureManagementCheckFailed,
          specialized_features::FeatureAccessFailure::
              kAccountCapabilitiesCheckFailed,
          specialized_features::FeatureAccessFailure::kCountryCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToFeatureManagement,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToAccountCapabilities,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToCountry, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest,
       CanShowUiFalseMultipleFeatureAccessCheckFailsWithConsentMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kConsentNotAccepted,
          specialized_features::FeatureAccessFailure::
              kFeatureManagementCheckFailed,
          specialized_features::FeatureAccessFailure::
              kAccountCapabilitiesCheckFailed,
          specialized_features::FeatureAccessFailure::kCountryCheckFailed}));

  ASSERT_FALSE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToFeatureManagement,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToAccountCapabilities,
      1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalseDueToCountry, 1);
  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedFalse, 1);
}

TEST_F(ScannerControllerTest, CanShowUiTrueWithoutConsentMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kConsentNotAccepted}));

  ASSERT_TRUE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedTrueWithoutConsent, 1);
}

TEST_F(ScannerControllerTest, CanShowUiTrueWithConsentMetrics) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              CheckFeatureAccess)
      .WillRepeatedly(Return(specialized_features::FeatureAccessFailureSet{}));

  ASSERT_TRUE(scanner_controller->CanShowUi());

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kCanShowUiReturnedTrueWithConsent, 1);
}

TEST_F(ScannerControllerTest,
       CannotShowConsentScreenEntryPointsIfOtherCheckFail) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kConsentNotAccepted,
          specialized_features::FeatureAccessFailure::
              kFeatureManagementCheckFailed}));

  EXPECT_FALSE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest,
       CanShowFeatureSettingsToggleIfDisabledInSettingsOnly) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kDisabledInSettings}));

  EXPECT_TRUE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest,
       CanShowFeatureSettingsToggleIfConsentNotAcceptedOnly) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kConsentNotAccepted}));

  EXPECT_TRUE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest,
       CanShowFeatureSettingsToggleIfDisallowedByPolicy) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{}));
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(ScannerEnterprisePolicy::kDisallowed));

  EXPECT_TRUE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest,
       CannotShowFeatureSettingsToggleIfDisabledIfOtherCheckFail) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ON_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
          CheckFeatureAccess)
      .WillByDefault(Return(specialized_features::FeatureAccessFailureSet{
          specialized_features::FeatureAccessFailure::kDisabledInSettings,
          specialized_features::FeatureAccessFailure::
              kFeatureManagementCheckFailed}));

  EXPECT_FALSE(scanner_controller->CanShowFeatureSettingsToggle());
}

TEST_F(ScannerControllerTest, FetchesActionsDuringActiveSession) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  output->add_objects()->add_actions()->mutable_new_event()->set_title(
      "Event title");
  EXPECT_CALL(*GetFakeScannerProfileScopedDelegate(*scanner_controller),
              FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(std::move(output), manta::MantaStatus()));

  EXPECT_TRUE(scanner_controller->FetchActionsForImage(
      /*jpeg_bytes=*/nullptr, actions_future.GetCallback()));

  EXPECT_THAT(actions_future.Take(), ValueIs(SizeIs(1)));
}

TEST_F(ScannerControllerTest, NoActionsFetchedWhenNoActiveSession) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);

  EXPECT_FALSE(scanner_controller->FetchActionsForImage(
      /*jpeg_bytes=*/nullptr, actions_future.GetCallback()));

  EXPECT_THAT(actions_future.Take(), ValueIs(IsEmpty()));
}

TEST_F(ScannerControllerTest, ResetsScannerSessionWhenActiveUserChanges) {
  SimulateUserLogin({"user1@gmail.com"});
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  EXPECT_TRUE(scanner_controller->HasActiveSessionForTesting());

  // Switch to a different user.
  SimulateUserLogin({"user2@gmail.com"});

  EXPECT_FALSE(scanner_controller->HasActiveSessionForTesting());
}

TEST_F(ScannerControllerTest, ShowsNotificationWhileExecutingNewEventAction) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()->add_actions()->mutable_new_event()->set_title(
      "Event 1");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback>
      fetch_action_details_future;
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(WithArg<2>(InvokeFuture(fetch_action_details_future)));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  // Notification should be shown while action is executing.
  EXPECT_THAT(message_center::MessageCenter::Get()->GetVisibleNotifications(),
              SizeIs(1));

  // Finish executing the action.
  fetch_action_details_future.Take().Run(
      std::make_unique<manta::proto::ScannerOutput>(output),
      manta::MantaStatus());

  // Notification should be hidden.
  EXPECT_THAT(message_center::MessageCenter::Get()->GetVisibleNotifications(),
              IsEmpty());
}

TEST_F(ScannerControllerTest,
       ShowsNotificationWhileExecutingCopyToClipboardAction) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  base::test::TestFuture<manta::ScannerProvider::ScannerProtoResponseCallback>
      fetch_action_details_future;
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(WithArg<2>(InvokeFuture(fetch_action_details_future)));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  // Notification should be shown while the action is executing.
  EXPECT_THAT(message_center::MessageCenter::Get()->GetVisibleNotifications(),
              SizeIs(1));

  // Finish executing the action.
  fetch_action_details_future.Take().Run(
      std::make_unique<manta::proto::ScannerOutput>(output),
      manta::MantaStatus());

  // Notification should be hidden.
  EXPECT_THAT(message_center::MessageCenter::Get()->GetVisibleNotifications(),
              IsEmpty());
}

TEST_F(ScannerControllerTest, ShowsToastAfterCopyToClipboardActionSuccess) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
}

TEST_F(ScannerControllerTest, DoesNotShowToastAfterNewEventActionSuccess) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()->add_actions()->mutable_new_event()->set_title(
      "Event title");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_FALSE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
}

TEST_F(ScannerControllerTest, ShowsToastAfterActionFailure) {
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  auto output = std::make_unique<manta::proto::ScannerOutput>();
  output->add_objects()->add_actions()->mutable_new_event()->set_title(
      "Event title");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(std::move(output), manta::MantaStatus()));
  // Mock a failure to fetch action details.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          /*output=*/nullptr,
          manta::MantaStatus{.status_code =
                                 manta::MantaStatusCode::kBackendFailure}));

  // Fetch an action and try to execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionFailureToastId));
}

TEST_F(ScannerControllerTest, ActionSuccessToastButtonOpensFeedbackDialog) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kScannerFeedbackToast);
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
  ToastOverlay* toast_overlay =
      Shell::Get()->toast_manager()->GetCurrentOverlayForTesting();
  ASSERT_TRUE(toast_overlay);
  views::Button* feedback_button = toast_overlay->button_for_testing();
  ASSERT_TRUE(feedback_button);

  FakeScannerDelegate& fake_scanner_delegate =
      *GetFakeScannerDelegate(*scanner_controller);
  base::test::TestFuture<const AccountId&, ScannerFeedbackInfo,
                         ScannerDelegate::SendFeedbackCallback>
      feedback_info_future;
  fake_scanner_delegate.SetOpenFeedbackDialogCallback(
      feedback_info_future.GetRepeatingCallback());

  LeftClickOn(feedback_button);

  auto [unused_account_id, feedback_dialog_info,
        unused_send_feedback_callback] = feedback_info_future.Take();
  EXPECT_EQ(feedback_dialog_info.action_details,
            "copy_to_clipboard.html_text: <b>Hello</b>\n");
}

TEST_F(ScannerControllerTest, ActionSuccessToastButtonEmitsMetric) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kScannerFeedbackToast);
  base::HistogramTester histogram_tester;
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  ASSERT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
  ToastOverlay* toast_overlay =
      Shell::Get()->toast_manager()->GetCurrentOverlayForTesting();
  ASSERT_TRUE(toast_overlay);
  views::Button* feedback_button = toast_overlay->button_for_testing();
  ASSERT_TRUE(feedback_button);

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kFeedbackFormOpened, 0);
  LeftClickOn(feedback_button);

  histogram_tester.ExpectBucketCount(
      "Ash.ScannerFeature.UserState",
      ScannerFeatureUserState::kFeedbackFormOpened, 1);
}

TEST_F(
    ScannerControllerTest,
    ActionSuccessToastDoesNotHaveButtonIfPolicyAllowedWithoutModelImprovement) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kScannerFeedbackToast);
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed,
      static_cast<int>(
          ScannerEnterprisePolicy::kAllowedWithoutModelImprovement));
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
  ToastOverlay* overlay =
      Shell::Get()->toast_manager()->GetCurrentOverlayForTesting();
  ASSERT_TRUE(overlay);
  views::Button* button = overlay->button_for_testing();
  EXPECT_FALSE(button);
}

TEST_F(ScannerControllerTest,
       ActionSuccessToastDoesNotHaveButtonIfPolicyInvalidValue) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kScannerFeedbackToast);
  Shell::Get()->session_controller()->GetActivePrefService()->SetInteger(
      prefs::kScannerEnterprisePolicyAllowed, 3);
  base::test::TestFuture<ScannerSession::FetchActionsResponse> actions_future;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  EXPECT_TRUE(scanner_controller->StartNewSession());
  manta::proto::ScannerOutput output;
  output.add_objects()
      ->add_actions()
      ->mutable_copy_to_clipboard()
      ->set_html_text("<b>Hello</b>");
  FakeScannerProfileScopedDelegate& fake_profile_scoped_delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  // Mock a successful action.
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus()));
  EXPECT_CALL(fake_profile_scoped_delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::make_unique<manta::proto::ScannerOutput>(output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  // Fetch an action and execute it.
  scanner_controller->FetchActionsForImage(/*jpeg_bytes=*/nullptr,
                                           actions_future.GetCallback());
  ScannerSession::FetchActionsResponse actions = actions_future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(ToastManager::Get()->IsToastShown(kScannerActionSuccessToastId));
  ToastOverlay* overlay =
      Shell::Get()->toast_manager()->GetCurrentOverlayForTesting();
  ASSERT_TRUE(overlay);
  views::Button* button = overlay->button_for_testing();
  EXPECT_FALSE(button);
}

TEST_F(ScannerControllerTest, OpenFeedbackDialogCallsDelegate) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  FakeScannerDelegate& fake_scanner_delegate =
      *GetFakeScannerDelegate(*scanner_controller);
  base::test::TestFuture<const AccountId&, ScannerFeedbackInfo,
                         ScannerDelegate::SendFeedbackCallback>
      feedback_info_future;
  fake_scanner_delegate.SetOpenFeedbackDialogCallback(
      feedback_info_future.GetRepeatingCallback());
  manta::proto::ScannerAction action;
  manta::proto::NewEventAction& new_event = *action.mutable_new_event();
  new_event.set_title("🌏");
  new_event.set_description("formerly \"Geo Sync\"");
  new_event.set_dates("20241014T160000/20241014T161500");
  new_event.set_location("Wonderland");
  auto image = base::MakeRefCounted<base::RefCountedString>("testimage");
  AccountId active_account =
      Shell::Get()->session_controller()->GetActiveAccountId();

  scanner_controller->OpenFeedbackDialog(active_account, std::move(action),
                                         std::move(image));

  auto [account_id, feedback_dialog_info, unused_send_feedback_callback] =
      feedback_info_future.Take();
  EXPECT_EQ(account_id, active_account);
  EXPECT_EQ(feedback_dialog_info.action_details,
            R"(new_event.dates: 20241014T160000/20241014T161500
new_event.description: formerly "Geo Sync"
new_event.location: Wonderland
new_event.title: 🌏
)");
  ASSERT_TRUE(feedback_dialog_info.screenshot);
  EXPECT_EQ(base::as_string_view(*feedback_dialog_info.screenshot),
            "testimage");
}

TEST_F(ScannerControllerTest, OpenFeedbackDialogCallbackSendsFeedback) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  FakeScannerDelegate& fake_scanner_delegate =
      *GetFakeScannerDelegate(*scanner_controller);
  base::test::TestFuture<const AccountId&, ScannerFeedbackInfo,
                         ScannerDelegate::SendFeedbackCallback>
      feedback_info_future;
  fake_scanner_delegate.SetOpenFeedbackDialogCallback(
      feedback_info_future.GetRepeatingCallback());
  base::test::TestFuture<std::string> description_future;
  AccountId active_account =
      Shell::Get()->session_controller()->GetActiveAccountId();
  EXPECT_CALL(mock_send_specialized_feature_feedback(),
              Run(/*account_id=*/active_account,
                  /*product_id=*/feedback::kScannerFeedbackProductId,
                  /*description=*/_,
                  /*image=*/Optional(Eq("testimage")),
                  /*image_mime_type=*/Optional(Eq("image/jpeg"))))
      .WillOnce(
          DoAll(WithArg<2>(InvokeFuture(description_future)), Return(true)));
  manta::proto::ScannerAction action;
  manta::proto::NewEventAction& new_event = *action.mutable_new_event();
  new_event.set_title("🌏");
  new_event.set_description("formerly \"Geo Sync\"");
  new_event.set_dates("20241014T160000/20241014T161500");
  new_event.set_location("Wonderland");
  auto image = base::MakeRefCounted<base::RefCountedString>("testimage");

  scanner_controller->OpenFeedbackDialog(active_account, std::move(action),
                                         std::move(image));
  auto [unused_account_id, feedback_dialog_info, send_feedback_callback] =
      feedback_info_future.Take();
  std::move(send_feedback_callback)
      .Run(std::move(feedback_dialog_info), "user description");

  std::string description = description_future.Take();
  std::vector<std::string_view> split_description =
      base::SplitStringPieceUsingSubstr(
          description,
          "\nuser_description:", base::WhitespaceHandling::KEEP_WHITESPACE,
          base::SplitResult::SPLIT_WANT_ALL);
  constexpr std::string_view kPrefix = "details:  ";
  ASSERT_THAT(split_description,
              ElementsAre(StartsWith(kPrefix), "  user description\n"));
  std::string_view details = split_description[0];
  details.remove_prefix(kPrefix.size());
  EXPECT_THAT(details, IsJson(R"json({
    "new_event": {
      "title": "🌏",
      "description": "formerly \"Geo Sync\"",
      "dates": "20241014T160000/20241014T161500",
      "location": "Wonderland",
    }
  })json"));
}

TEST_F(ScannerControllerTest,
       OpenFeedbackDialogSendFeedbackCallbackSendsMetric) {
  base::HistogramTester histogram_tester;
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  FakeScannerDelegate& fake_scanner_delegate =
      *GetFakeScannerDelegate(*scanner_controller);
  base::test::TestFuture<const AccountId&, ScannerFeedbackInfo,
                         ScannerDelegate::SendFeedbackCallback>
      feedback_info_future;
  fake_scanner_delegate.SetOpenFeedbackDialogCallback(
      feedback_info_future.GetRepeatingCallback());
  EXPECT_CALL(mock_send_specialized_feature_feedback(), Run);
  manta::proto::ScannerAction action;
  action.mutable_copy_to_clipboard()->set_html_text("<b>Hello</b>");

  scanner_controller->OpenFeedbackDialog(
      Shell::Get()->session_controller()->GetActiveAccountId(),
      std::move(action),
      /*screenshot=*/base::MakeRefCounted<base::RefCountedString>("testimage"));
  auto [unused_account_id, feedback_dialog_info, send_feedback_callback] =
      feedback_info_future.Take();
  histogram_tester.ExpectBucketCount("Ash.ScannerFeature.UserState",
                                     ScannerFeatureUserState::kFeedbackSent, 0);
  std::move(send_feedback_callback)
      .Run(std::move(feedback_dialog_info), "feedback");

  histogram_tester.ExpectBucketCount("Ash.ScannerFeature.UserState",
                                     ScannerFeatureUserState::kFeedbackSent, 1);
}

TEST_F(ScannerControllerTest,
       OpenFeedbackDialogCallbackSendsFeedbackWithOriginalAccount) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  FakeScannerDelegate& fake_scanner_delegate =
      *GetFakeScannerDelegate(*scanner_controller);
  base::test::TestFuture<const AccountId&, ScannerFeedbackInfo,
                         ScannerDelegate::SendFeedbackCallback>
      feedback_info_future;
  fake_scanner_delegate.SetOpenFeedbackDialogCallback(
      feedback_info_future.GetRepeatingCallback());
  AccountId original_account =
      Shell::Get()->session_controller()->GetActiveAccountId();
  EXPECT_CALL(mock_send_specialized_feature_feedback(),
              Run(/*account_id=*/original_account,
                  /*product_id=*/_,
                  /*description=*/_,
                  /*image=*/_,
                  /*image_mime_type=*/_))
      .WillOnce(Return(true));

  scanner_controller->OpenFeedbackDialog(
      original_account, manta::proto::ScannerAction(),
      base::MakeRefCounted<base::RefCountedString>());
  auto [account_id, feedback_dialog_info, send_feedback_callback] =
      feedback_info_future.Take();
  ASSERT_EQ(Shell::Get()->session_controller()->GetActiveAccountId(),
            original_account);
  AccountId new_account =
      AccountId::FromUserEmailGaiaId("user@test.com", GaiaId("fakegaia"));
  SimulateUserLogin(new_account);
  ASSERT_NE(Shell::Get()->session_controller()->GetActiveAccountId(),
            original_account);
  std::move(send_feedback_callback)
      .Run(std::move(feedback_dialog_info), "user description");
}

TEST_F(ScannerControllerTest, RunningActionFailsIfActionDetailsFails) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          nullptr, manta::MantaStatus{
                       .status_code = manta::MantaStatusCode::kInvalidInput}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));

  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_FALSE(action_finished_future.Get());
}

TEST_F(ScannerControllerTest,
       RunningActionFailsIfActionDetailsHaveMultipleObjects) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  auto output_with_multiple_objects =
      std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::move(output_with_multiple_objects),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_FALSE(action_finished_future.Get());
}

TEST_F(ScannerControllerTest,
       RunningActionFailsIfActionDetailsHaveMultipleActions) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  auto output_with_multiple_actions =
      std::make_unique<manta::proto::ScannerOutput>();
  manta::proto::ScannerObject& object = *unpopulated_output->add_objects();
  object.add_actions()->mutable_new_event();
  object.add_actions()->mutable_new_event();
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::move(output_with_multiple_actions),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_FALSE(action_finished_future.Get());
}

TEST_F(ScannerControllerTest,
       RunningActionFailsIfActionDetailsHaveDifferentActionCase) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  auto output_with_different_action =
      std::make_unique<manta::proto::ScannerOutput>();
  manta::proto::ScannerObject& object = *unpopulated_output->add_objects();
  object.add_actions()->mutable_new_contact();
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::move(output_with_different_action),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_FALSE(action_finished_future.Get());
}

TEST_F(ScannerControllerTest,
       RunningActionsCallsFetchActionDetailsForImageWithResizedImage) {
  scoped_refptr<base::RefCountedMemory> jpeg_bytes =
      MakeJpegBytes(/*width=*/2300, /*height=*/23000);
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage(
                            Pointee(ResultOf(
                                "decoding JPEG",
                                [](const base::RefCountedMemory& bytes) {
                                  return gfx::JPEGCodec::Decode(bytes);
                                },
                                AllOf(Property(&SkBitmap::width, 230),
                                      Property(&SkBitmap::height, 2300)))),
                            _, _))
      .WillOnce(RunOnceCallback<2>(
          nullptr, manta::MantaStatus{
                       .status_code = manta::MantaStatusCode::kInvalidInput}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(jpeg_bytes, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);
  ASSERT_TRUE(action_finished_future.IsReady());
}

TEST_F(ScannerControllerTest,
       RunningActionsCallsFetchActionDetailsForImageWithUnpopulatedAction) {
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  manta::proto::ScannerAction unpopulated_action;
  unpopulated_action.mutable_new_event()->set_title("Unpopulated event");
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  *unpopulated_output->add_objects()->add_actions() = unpopulated_action;
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate,
              FetchActionDetailsForImage(_, EqualsProto(unpopulated_action), _))
      .WillOnce(RunOnceCallback<2>(
          nullptr, manta::MantaStatus{
                       .status_code = manta::MantaStatusCode::kInvalidInput}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);
  ASSERT_TRUE(action_finished_future.IsReady());
}

TEST_F(ScannerControllerTest, RunningNewEventActionOpensUrl) {
  EXPECT_CALL(mock_new_window_delegate(),
              OpenUrl(Property("spec", &GURL::spec,
                               "https://calendar.google.com/calendar/render"
                               "?action=TEMPLATE"
                               "&text=%F0%9F%8C%8F"
                               "&details=formerly+%22Geo+Sync%22"
                               "&dates=20241014T160000%2F20241014T161500"
                               "&location=Wonderland"),
                      _, _))
      .Times(1);
  ScannerController* scanner_controller = Shell::Get()->scanner_controller();
  ASSERT_TRUE(scanner_controller);
  ScannerSession* session = scanner_controller->StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(*scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_event();
  manta::proto::NewEventAction event_action;
  event_action.set_title("🌏");
  event_action.set_description("formerly \"Geo Sync\"");
  event_action.set_dates("20241014T160000/20241014T161500");
  event_action.set_location("Wonderland");
  auto populated_output = std::make_unique<manta::proto::ScannerOutput>();
  *populated_output->add_objects()->add_actions()->mutable_new_event() =
      std::move(event_action);
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::move(populated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller->SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller->ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(action_finished_future.Get());
}

// This test cannot use AshTestBase as it requires an IO thread.
TEST(ScannerControllerNoFixtureTest, RunningNewContactActionOpensUrl) {
  // ScannerController dependencies:
  // A task environment with an IO thread to use `TestSharedURLLoaderFactory`.
  base::test::TaskEnvironment task_environment(
      base::test::TaskEnvironment::MainThreadType::IO);
  // A session controller for `ScopedSessionObserver`.
  SessionControllerImpl session_controller;
  // A message center for showing notifications.
  message_center::MessageCenter::Initialize();
  // A toast manager for showing toasts.
  MockToastManager toast_manager;
  // A new window delegate for opening the URL in test.
  MockNewWindowDelegate mock_new_window_delegate;
  EXPECT_CALL(mock_new_window_delegate,
              OpenUrl(Property("spec", &GURL::spec,
                               "https://contacts.google.com/person/c1?edit=1"),
                      _, _))
      .Times(1);
  ScannerController scanner_controller(std::make_unique<FakeScannerDelegate>(),
                                       session_controller,
                                       /*screen_pinning_controller=*/nullptr);
  ScannerSession* session = scanner_controller.StartNewSession();
  ASSERT_TRUE(session);
  FakeScannerProfileScopedDelegate& delegate =
      *GetFakeScannerProfileScopedDelegate(scanner_controller);
  auto unpopulated_output = std::make_unique<manta::proto::ScannerOutput>();
  unpopulated_output->add_objects()->add_actions()->mutable_new_contact();
  manta::proto::NewContactAction contact_action;
  contact_action.set_given_name("André");
  contact_action.set_family_name("François");
  contact_action.set_email("afrancois@example.com");
  contact_action.set_phone("+61400000000");
  auto populated_output = std::make_unique<manta::proto::ScannerOutput>();
  *populated_output->add_objects()->add_actions()->mutable_new_contact() =
      std::move(contact_action);
  EXPECT_CALL(delegate, FetchActionsForImage)
      .WillOnce(RunOnceCallback<1>(
          std::move(unpopulated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  EXPECT_CALL(delegate, FetchActionDetailsForImage)
      .WillOnce(RunOnceCallback<2>(
          std::move(populated_output),
          manta::MantaStatus{.status_code = manta::MantaStatusCode::kOk}));
  base::MockCallback<
      net::test_server::EmbeddedTestServer::HandleRequestCallback>
      request_callback;
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_code(net::HttpStatusCode::HTTP_OK);
  response->set_content(R"json({"resourceName": "people/c1"})json");
  response->set_content_type("application/json");
  EXPECT_CALL(request_callback, Run).WillOnce(Return(std::move(response)));
  delegate.SetRequestCallback(request_callback.Get());

  base::test::TestFuture<ScannerSession::FetchActionsResponse> future;
  session->FetchActionsForImage(nullptr, future.GetCallback());
  ScannerSession::FetchActionsResponse actions = future.Take();
  ASSERT_THAT(actions, ValueIs(SizeIs(1)));
  base::test::TestFuture<bool> action_finished_future;
  scanner_controller.SetOnActionFinishedForTesting(
      action_finished_future.GetCallback());
  scanner_controller.ExecuteAction(actions.value()[0]);

  EXPECT_TRUE(action_finished_future.Get());

  message_center::MessageCenter::Shutdown();
}

}  // namespace

}  // namespace ash
