// Copyright 2020 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/signin/dice_web_signin_interceptor.h"

#include <string>

#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/run_until.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/signin/profile_management_disclaimer_service.h"
#include "chrome/browser/enterprise/signin/profile_management_disclaimer_service_factory.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_init_params.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_manager_observer.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/signin/account_preview_data_service_factory.h"
#include "chrome/browser/signin/chrome_signin_pref_names.h"
#include "chrome/browser/signin/dice_intercepted_session_startup_helper.h"
#include "chrome/browser/signin/dice_web_signin_interceptor_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
#include "chrome/browser/signin/signin_browser_test_base.h"
#include "chrome/browser/signin/web_signin_interceptor.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/global_browser_collection.h"
#include "chrome/browser/ui/hats/hats_service_factory.h"
#include "chrome/browser/ui/hats/mock_hats_service.h"
#include "chrome/browser/ui/hats/survey_config.h"
#include "chrome/browser/ui/signin/dice_web_signin_interceptor_delegate.h"
#include "chrome/browser/ui/signin/signin_view_controller.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/ui/webui/settings/people_handler.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/profile_waiter.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/account_id/account_id.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/metrics/profile_metrics_service.h"
#include "components/password_manager/core/browser/features/password_manager_features_util.h"
#include "components/prefs/pref_service.h"
#include "components/search_engines/search_engines_pref_names.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/template_url.h"
#include "components/search_engines/template_url_service.h"
#include "components/signin/core/browser/test_account_preview_data_service.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_prefs.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/signin/public/identity_manager/test_identity_manager_observer.h"
#include "components/signin/public/identity_manager/tribool.h"
#include "components/sync/base/features.h"
#include "components/sync/base/pref_names.h"
#include "components/sync/base/user_selectable_type.h"
#include "components/sync/protocol/sync_enums.pb.h"
#include "components/sync/service/sync_service.h"
#include "components/sync/service/sync_user_settings.h"
#include "components/version_info/version_info.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "google_apis/gaia/gaia_id.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/dns/mock_host_resolver.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
#include "url/gurl.h"

using testing::_;
using testing::Eq;
using testing::IsEmpty;
using testing::Pair;
using testing::UnorderedElementsAre;

namespace {

const char kCustomSearchEngineDomain[] = "bar.com";

// Fake response for OAuth multilogin.
const char kMultiloginSuccessResponse[] =
    R"()]}'
       {
         "status": "OK",
         "cookies":[
           {
             "name":"SID",
             "value":"SID_value",
             "domain":".google.fr",
             "path":"/",
             "isSecure":true,
             "isHttpOnly":false,
             "priority":"HIGH",
             "maxAge":63070000
           }
         ]
       }
      )";

class FakeDiceWebSigninInterceptorDelegate;

class FakeBubbleHandle final : public ScopedWebSigninInterceptionBubbleHandle {
 public:
  ~FakeBubbleHandle() override = default;

  base::WeakPtr<FakeBubbleHandle> AsWeakPtr() {
    return weak_ptr_factory_.GetWeakPtr();
  }

 private:
  base::WeakPtrFactory<FakeBubbleHandle> weak_ptr_factory_{this};
};

// Dummy interception delegate that automatically accepts multi user
// interception.
class FakeDiceWebSigninInterceptorDelegate
    : public DiceWebSigninInterceptorDelegate {
 public:
  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
  ShowSigninInterceptionBubble(
      content::WebContents* web_contents,
      const BubbleParameters& bubble_parameters,
      base::OnceCallback<void(SigninInterceptionResult)> callback) override {
    EXPECT_EQ(bubble_parameters.interception_type, expected_interception_type_);
    last_bubble_parameters_ = bubble_parameters;
    auto bubble_handle = std::make_unique<FakeBubbleHandle>();
    weak_bubble_handle_ = bubble_handle->AsWeakPtr();
    // The callback must not be called synchronously (see the documentation for
    // ShowSigninInterceptionBubble).
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), expected_interception_result_));
    return bubble_handle;
  }

  void ShowFirstRunExperienceInNewProfile(
      BrowserWindowInterface* browser,
      const CoreAccountId& account_id,
      WebSigninInterceptor::SigninInterceptionType interception_type) override {
    EXPECT_FALSE(fre_browser_)
        << "First run experience must be shown only once.";
    EXPECT_EQ(interception_type, expected_interception_type_);
    fre_browser_ = browser;
    fre_account_id_ = account_id;
  }

  BrowserWindowInterface* fre_browser() { return fre_browser_; }

  const CoreAccountId& fre_account_id() { return fre_account_id_; }

  const std::optional<BubbleParameters>& last_bubble_parameters() const {
    return last_bubble_parameters_;
  }

  void set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType type) {
    expected_interception_type_ = type;
  }

  void set_expected_interception_result(SigninInterceptionResult result) {
    expected_interception_result_ = result;
  }

  bool intercept_bubble_shown() const { return weak_bubble_handle_.get(); }

  bool intercept_bubble_destroyed() const {
    return weak_bubble_handle_.WasInvalidated();
  }

  void ShowSigninError(content::WebContents* web_contents,
                       const SigninUIError& error) override {
    signin_error_ = error;
  }

  const std::optional<SigninUIError>& signin_error() const {
    return signin_error_;
  }

 private:
  raw_ptr<BrowserWindowInterface, AcrossTasksDanglingUntriaged> fre_browser_ =
      nullptr;
  CoreAccountId fre_account_id_;
  WebSigninInterceptor::SigninInterceptionType expected_interception_type_ =
      WebSigninInterceptor::SigninInterceptionType::kMultiUser;
  SigninInterceptionResult expected_interception_result_ =
      SigninInterceptionResult::kAccepted;
  std::optional<BubbleParameters> last_bubble_parameters_;
  std::optional<SigninUIError> signin_error_;
  base::WeakPtr<FakeBubbleHandle> weak_bubble_handle_;
};

// Runs the interception and returns the new profile that was created.
Profile* InterceptAndWaitProfileCreation(content::WebContents* contents,
                                         const CoreAccountId& account_id) {
  ProfileWaiter profile_waiter;
  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(
          Profile::FromBrowserContext(contents->GetBrowserContext()));
  interceptor->MaybeInterceptWebSignin(
      contents, account_id, signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  // Simulate the terminal session completion since the browser test bypasses
  // the actual DiceResponseHandler token exchange flow.
  interceptor->OnDiceSigninSessionComplete(account_id, {});
  // Wait for the interception to be complete.
  return profile_waiter.WaitForProfileAdded();
}

// Checks that the interception histograms were correctly recorded.
void CheckHistograms(const base::HistogramTester& histogram_tester,
                     SigninInterceptionHeuristicOutcome outcome) {
  histogram_tester.ExpectUniqueSample("Signin.Intercept.HeuristicOutcome",
                                      outcome, 1);
}

void SetUserSelectedDefaultSearchProvider(
    TemplateURLService* template_url_service) {
  TemplateURLData data;
  data.SetShortName(base::UTF8ToUTF16(std::string(kCustomSearchEngineDomain)));
  data.SetKeyword(base::UTF8ToUTF16(std::string(kCustomSearchEngineDomain)));
  data.SetURL("https://" + std::string(kCustomSearchEngineDomain) +
              "url?bar={searchTerms}");
  data.new_tab_url =
      "https://" + std::string(kCustomSearchEngineDomain) + "newtab";
  data.alternate_urls.push_back("https://" +
                                std::string(kCustomSearchEngineDomain) +
                                "alt#quux={searchTerms}");

  TemplateURL* template_url =
      template_url_service->Add(std::make_unique<TemplateURL>(data));
  template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
}

}  // namespace

class DiceWebSigninInterceptorBrowserTest : public SigninBrowserTestBase {
 public:
  DiceWebSigninInterceptorBrowserTest()
      : SigninBrowserTestBase(/*use_main_profile=*/true) {}

  content::WebContents* AddTab(const GURL& url) {
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

  FakeDiceWebSigninInterceptorDelegate* GetInterceptorDelegate(
      Profile* profile) {
    // Make sure the interceptor has been created.
    DiceWebSigninInterceptorFactory::GetForProfile(profile);
    FakeDiceWebSigninInterceptorDelegate* interceptor_delegate =
        interceptor_delegates_[profile];
    return interceptor_delegate;
  }

  signin::TestAccountPreviewDataService* GetTestAccountPreviewDataService(
      Profile* profile) {
    // Make sure the service has been created.
    AccountPreviewDataServiceFactory::GetForProfile(profile);
    return test_account_preview_data_services_[profile];
  }

  void SetupGaiaResponses() {
    // Instantly return from Gaia calls, to avoid timing out when injecting the
    // account in the new profile.
    network::TestURLLoaderFactory* loader_factory = test_url_loader_factory();
    loader_factory->SetInterceptor(base::BindLambdaForTesting(
        [loader_factory](const network::ResourceRequest& request) {
          std::string path = request.url.GetPath();
          if (path == "/ListAccounts" || path == "/GetCheckConnectionInfo") {
            loader_factory->AddResponse(request.url.spec(), std::string());
            return;
          }
          if (path == "/oauth/multilogin") {
            loader_factory->AddResponse(request.url.spec(),
                                        kMultiloginSuccessResponse);
            return;
          }
        }));
  }

  AccountInfo MakeAccountInfoAvailableAndUpdate(
      std::string_view email,
      const std::string& hosted_domain) {
    AccountInfo account_info = identity_test_env()->MakeAccountAvailable(email);
    // Fill the account info, in particular for the hosted_domain field.
    account_info = AccountInfo::Builder(account_info)
                       .SetFullName("fullname")
                       .SetGivenName("givenname")
                       .SetHostedDomain(hosted_domain)
                       .SetAvatarUrl("https://example.com")
                       .SetLocale("en")
                       .Build();

    // Fill in the required account capabilities for the sign in intercept.
    AccountCapabilitiesTestMutator mutator(&account_info);
    mutator.set_is_subject_to_parental_controls(false);
    mutator.set_is_subject_to_enterprise_features(!hosted_domain.empty());
    mutator.set_is_subject_to_account_level_enterprise_policies(
        !hosted_domain.empty());

    DCHECK(account_info.IsValid());
    identity_test_env()->UpdateAccountInfoForAccount(account_info);
    return account_info;
  }

  // InProcessBrowserTest:
  void SetUpOnMainThread() override {
    SigninBrowserTestBase::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
    DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
        ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
            policy::ProfileSeparationPolicies(""));
  }

  void TearDownOnMainThread() override {
    interceptor_delegates_.clear();
    test_account_preview_data_services_.clear();
    SigninBrowserTestBase::TearDownOnMainThread();
  }

 private:
  void OnWillCreateBrowserContextServices(
      content::BrowserContext* context) override {
    SigninBrowserTestBase::OnWillCreateBrowserContextServices(context);
    DiceWebSigninInterceptorFactory::GetInstance()->SetTestingFactory(
        context,
        base::BindRepeating(&DiceWebSigninInterceptorBrowserTest::
                                BuildDiceWebSigninInterceptorWithFakeDelegate,
                            base::Unretained(this)));
    AccountPreviewDataServiceFactory::GetInstance()->SetTestingFactory(
        context, base::BindRepeating(&DiceWebSigninInterceptorBrowserTest::
                                         BuildTestAccountPreviewDataService,
                                     base::Unretained(this)));
  }

  // Builds a DiceWebSigninInterceptor with a fake delegate. To be used as a
  // testing factory.
  std::unique_ptr<KeyedService> BuildDiceWebSigninInterceptorWithFakeDelegate(
      content::BrowserContext* context) {
    std::unique_ptr<FakeDiceWebSigninInterceptorDelegate> fake_delegate =
        std::make_unique<FakeDiceWebSigninInterceptorDelegate>();
    interceptor_delegates_[context] = fake_delegate.get();
    Profile* profile = Profile::FromBrowserContext(context);
    return std::make_unique<DiceWebSigninInterceptor>(
        profile, std::move(fake_delegate), &profile_metrics_service_);
  }

  std::unique_ptr<KeyedService> BuildTestAccountPreviewDataService(
      content::BrowserContext* context) {
    auto test_service =
        std::make_unique<signin::TestAccountPreviewDataService>();
    test_account_preview_data_services_[context] = test_service.get();
    return test_service;
  }

  web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;

  absl::flat_hash_map<content::BrowserContext*,
                      raw_ptr<FakeDiceWebSigninInterceptorDelegate>>
      interceptor_delegates_;
  absl::flat_hash_map<content::BrowserContext*,
                      raw_ptr<signin::TestAccountPreviewDataService>>
      test_account_preview_data_services_;
  metrics::ProfileMetricsService profile_metrics_service_{
      metrics::ProfileMetricsContext(1)};
};

// Tests the complete profile switch flow when the profile is not loaded.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, SwitchAndLoad) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());
  // Add a profile in the cache (simulate the profile on disk).
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  ProfileAttributesStorage* profile_storage =
      &profile_manager->GetProfileAttributesStorage();
  const base::FilePath profile_path =
      profile_manager->GenerateNextProfileDirectoryPath();
  ProfileAttributesInitParams params;
  params.profile_path = profile_path;
  params.profile_name = u"TestProfileName";
  params.gaia_id = account_info.GetGaiaId();
  params.user_name = base::UTF8ToUTF16(account_info.GetEmail());
  profile_storage->AddProfile(std::move(params));
  ProfileAttributesEntry* entry =
      profile_storage->GetProfileAttributesWithPath(profile_path);
  ASSERT_TRUE(entry);
  ASSERT_EQ(entry->GetGAIAId(), account_info.GetGaiaId());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  ASSERT_TRUE(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  // Check that the right profile was opened.
  EXPECT_EQ(new_profile->GetPath(), profile_path);

  // Add the account to the cookies (simulates the account reconcilor).
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  signin::SetCookieAccounts(
      new_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});
  const BrowserWindowInterface* const added_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(added_browser);

  // A browser has been created for the new profile and the tab was moved there.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(added_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
  // Interception bubble was closed.
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  // First run experience was not shown.
  EXPECT_EQ(GetInterceptorDelegate(new_profile)->fre_browser(), nullptr);
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// Tests the complete profile switch flow when the profile is already loaded.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, SwitchAlreadyOpen) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());
  // Create another profile with a browser window.
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  const base::FilePath profile_path =
      profile_manager->GenerateNextProfileDirectoryPath();
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  base::RunLoop loop;
  Profile* other_profile = nullptr;
  profiles::SwitchToProfile(
      profile_path, /*always_create=*/true,
      base::BindLambdaForTesting(
          [&other_profile, &loop](BrowserWindowInterface* browser) {
            other_profile = browser->GetProfile();
            loop.Quit();
          }));
  loop.Run();
  ASSERT_TRUE(other_profile);
  const BrowserWindowInterface* const other_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(other_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  ASSERT_EQ(other_browser->GetProfile(), other_profile);
  // Add the account to the other profile.
  signin::IdentityManager* other_identity_manager =
      IdentityManagerFactory::GetForProfile(other_profile);
  signin::MakePrimaryAccountAvailable(other_identity_manager,
                                      account_info.GetEmail(),
                                      signin::ConsentLevel::kSignin);

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();
  int other_original_tab_count = other_browser->GetTabStripModel()->count();

  // Start the interception.
  GetInterceptorDelegate(GetProfile())
      ->set_expected_interception_type(
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  // Add the account to the cookies (simulates the account reconcilor).
  signin::SetCookieAccounts(
      other_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});

  // Wait until the tab is moved to the other browser.
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return browser()->GetTabStripModel()->count() == original_tab_count - 1;
  }));

  // The tab was moved to the new browser.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(other_browser->GetTabStripModel()->count(),
            other_original_tab_count + 1);
  EXPECT_EQ(other_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
  // First run experience was not shown.
  EXPECT_EQ(GetInterceptorDelegate(other_profile)->fre_browser(), nullptr);
  EXPECT_EQ(GetInterceptorDelegate(GetProfile())->fre_browser(), nullptr);
}

// Custom fixture that maps GAIA URLs to the local HTTPS test server and
// overrides GaiaUrls.
class DiceWebSigninInterceptorGaiaBrowserTest
    : public DiceWebSigninInterceptorBrowserTest {
 public:
  DiceWebSigninInterceptorGaiaBrowserTest()
      : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
    https_server_.AddDefaultHandlers(
        base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
    https_server_.SetCertHostnames({"accounts.google.com"});
  }

  void SetUpOnMainThread() override {
    DiceWebSigninInterceptorBrowserTest::SetUpOnMainThread();

    // Map accounts.google.com to 127.0.0.1.
    host_resolver()->AddRule("accounts.google.com", "127.0.0.1");

    // Start the HTTPS server.
    ASSERT_TRUE(https_server_.Start());

    // Get the local HTTPS test server URL for accounts.google.com.
    GURL gaia_test_url = https_server_.GetURL("accounts.google.com", "/");

    // Modify the command line to override the Gaia URL.
    base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
        "gaia-url", gaia_test_url.spec());

    // Re-create GaiaUrls for testing to pick up the new command line switch.
    custom_gaia_urls_ = std::make_unique<GaiaUrls>();
    GaiaUrls::SetInstanceForTesting(custom_gaia_urls_.get());
  }

  void TearDownOnMainThread() override {
    // Reset GaiaUrls override.
    GaiaUrls::SetInstanceForTesting(nullptr);
    custom_gaia_urls_.reset();

    DiceWebSigninInterceptorBrowserTest::TearDownOnMainThread();
  }

  net::EmbeddedTestServer* gaia_server() { return &https_server_; }

 private:
  net::EmbeddedTestServer https_server_;
  std::unique_ptr<GaiaUrls> custom_gaia_urls_;
};

// Tests that performing a Gaia navigation during the profile switch flow does
// not trigger a reentrant iteration crash in AccountReconcilor observers.
// Regression test for b/504560109.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorGaiaBrowserTest,
                       SwitchAlreadyOpen_GaiaNavigationCrashRegression) {
  base::HistogramTester histogram_tester;

  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());
  // Create another profile with a browser window.
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  const base::FilePath profile_path =
      profile_manager->GenerateNextProfileDirectoryPath();
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  base::RunLoop loop;
  Profile* other_profile = nullptr;
  profiles::SwitchToProfile(
      profile_path, /*always_create=*/true,
      base::BindLambdaForTesting(
          [&other_profile, &loop](BrowserWindowInterface* browser) {
            other_profile = browser->GetProfile();
            loop.Quit();
          }));
  loop.Run();
  ASSERT_TRUE(other_profile);
  const BrowserWindowInterface* const other_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(other_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  ASSERT_EQ(other_browser->GetProfile(), other_profile);
  // Add the account to the other profile.
  signin::IdentityManager* other_identity_manager =
      IdentityManagerFactory::GetForProfile(other_profile);
  signin::MakePrimaryAccountAvailable(other_identity_manager,
                                      account_info.GetEmail(),
                                      signin::ConsentLevel::kSignin);

  // Add a tab with a GAIA URL!
  GURL intercepted_url =
      gaia_server()->GetURL("accounts.google.com", "/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();
  int other_original_tab_count = other_browser->GetTabStripModel()->count();

  // Start the interception.
  GetInterceptorDelegate(GetProfile())
      ->set_expected_interception_type(
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitch);
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  // Add the account to the cookies (simulates the account reconcilor).
  signin::SetCookieAccounts(
      other_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});

  // Wait until the tab is moved to the other browser.
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return browser()->GetTabStripModel()->count() == original_tab_count - 1;
  }));

  // The tab was moved to the new browser.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(other_browser->GetTabStripModel()->count(),
            other_original_tab_count + 1);
  EXPECT_EQ(other_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
}

// Close the source tab during the interception and check that the NTP is opened
// in the new profile (regression test for https://crbug.com/40159041).
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, CloseSourceTab) {
  // Setup profile for interception.
  AccountInfo primary_account_info =
      identity_test_env()->MakePrimaryAccountAvailable(
          "alice@gmail.com", signin::ConsentLevel::kSignin);

  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "bob@example.com", /*hosted_domain=*/std::string());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  ProfileWaiter profile_waiter;
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(
          Profile::FromBrowserContext(contents->GetBrowserContext()));
  interceptor->MaybeInterceptWebSignin(
      contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});
  // Close the source tab during the profile creation.
  contents->Close();
  // Wait for the interception to be complete.
  Profile* new_profile = profile_waiter.WaitForProfileAdded();
  ASSERT_TRUE(new_profile);
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  // Add the account to the cookies (simulates the account reconcilor).
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  signin::SetCookieAccounts(
      new_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});
  const BrowserWindowInterface* const added_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(added_browser);

  // A browser has been created for the new profile on the new tab page.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(added_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            GURL("chrome://newtab/"));
}

class DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest
    : public DiceWebSigninInterceptorBrowserTest {
 public:
  ChromeSigninUserChoice GetChromeSigninUserChoicePref(
      const AccountInfo& account_info) {
    return SigninPrefs(*GetProfile()->GetPrefs())
        .GetChromeSigninInterceptionUserChoice(account_info.GetGaiaId());
  }

  int GetChromeSigninInterceptDismissCountPref(
      const AccountInfo& account_info) {
    return SigninPrefs(*GetProfile()->GetPrefs())
        .GetChromeSigninInterceptionDismissCount(account_info.GetGaiaId());
  }

  void Signout() { identity_test_env()->ClearPrimaryAccount(); }

  FakeDiceWebSigninInterceptorDelegate* ShowSigninBubble(
      const AccountInfo& account_info,
      std::optional<SigninInterceptionResult> expected_result) {
    GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
    content::WebContents* contents = AddTab(intercepted_url);

    // Set up the result expectations.
    FakeDiceWebSigninInterceptorDelegate* interceptor_delegate =
        GetInterceptorDelegate(GetProfile());
    interceptor_delegate->set_expected_interception_type(
        WebSigninInterceptor::SigninInterceptionType::kChromeSignin);
    if (expected_result.has_value()) {
      interceptor_delegate->set_expected_interception_result(
          expected_result.value());
    }

    DiceWebSigninInterceptor* interceptor =
        DiceWebSigninInterceptorFactory::GetForProfile(
            Profile::FromBrowserContext(contents->GetBrowserContext()));
    interceptor->MaybeInterceptWebSignin(
        contents, account_info.GetAccountId(),
        signin_metrics::AccessPoint::kWebSignin,
        /*is_new_account=*/true,
        /*is_sync_signin=*/false,
        /*primary_is_connected=*/signin::Tribool::kUnknown);

    return interceptor_delegate;
  }

  void ShowAndCompleteSigninBubbleWithResult(
      const AccountInfo& account_info,
      SigninInterceptionResult expected_result) {
    FakeDiceWebSigninInterceptorDelegate* interceptor_delegate =
        ShowSigninBubble(account_info, expected_result);

    // Bubble should be shown following the intercept.
    EXPECT_TRUE(interceptor_delegate->intercept_bubble_shown());

    // The handling of the response to the bubble is done asynchronously in
    // `FakeDiceWebSigninInterceptorDelegate::ShowSigninInterceptionBubble()`.
    base::RunLoop().RunUntilIdle();

    // Following the result the bubble should have been destroyed.
    EXPECT_TRUE(interceptor_delegate->intercept_bubble_destroyed());
  }

  // Attempts to show the Chrome SigninBubble, checks that it doesn't.
  void ExpectAttemptToShowChromeSigninBubbleNotToShow(const AccountInfo& info) {
    FakeDiceWebSigninInterceptorDelegate* delegate =
        ShowSigninBubble(info, /*expected_result=*/std::nullopt);
    EXPECT_FALSE(delegate->intercept_bubble_shown());
  }

  bool IsChromeSignedIn() const {
    return identity_manager()->HasPrimaryAccount(signin::ConsentLevel::kSignin);
  }

  void SetSignoutAllowed(bool allow) {
    // Accepting management in order not to get signed out when restarting the
    // browser. Since this test uses the fake IdentityManager cookies will not
    // be saved on disc, therefore unable to find them back on startup which is
    // causing a startup signout. Managed accounts cannot be signed out which is
    // a workaround not to be signed out on Chrome restart.
    enterprise_util::SetUserAcceptedAccountManagement(GetProfile(), !allow);
  }
};

class DiceWebSigninInterceptorWithHatsSurveyBrowserTest
    : public DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest {
 public:
  DiceWebSigninInterceptorWithHatsSurveyBrowserTest() {
    feature_list_.InitWithFeatures(
        /*enabled_features=*/
        {{switches::kChromeIdentitySurveyDiceWebSigninAccepted,
          switches::kChromeIdentitySurveyDiceWebSigninDeclined}},
        /*disabled_features=*/{});
  }

  void SetUpOnMainThread() override {
    DiceWebSigninInterceptorBrowserTest::SetUpOnMainThread();
    mock_hats_service_ = static_cast<MockHatsService*>(
        HatsServiceFactory::GetInstance()->SetTestingFactoryAndUse(
            browser()->GetProfile(),
            base::BindRepeating(&BuildMockHatsService)));
  }

  void TearDownOnMainThread() override {
    mock_hats_service_ = nullptr;
    DiceWebSigninInterceptorBrowserTest::TearDownOnMainThread();
  }

  MockHatsService* mock_hats_service() { return mock_hats_service_; }

 private:
  raw_ptr<MockHatsService> mock_hats_service_ = nullptr;
  base::test::ScopedFeatureList feature_list_;
};

// Tests that a HaTS survey is launched when users accept the intercept signin
// promo.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorWithHatsSurveyBrowserTest,
                       ShowHatsSurveyOnChromeSigninInterceptAccepted) {
  // Setup account for interception.
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());

  // Makes sure Chrome is not signed in to trigger the intercept bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  EXPECT_CALL(
      *mock_hats_service(),
      LaunchDelayedSurvey(
          kHatsSurveyTriggerIdentityDiceWebSigninAccepted, _, _,
          UnorderedElementsAre(
              Pair("Channel", _),
              Pair("Chrome Version", version_info::GetVersion().GetString()),
              Pair("Number of Chrome Profiles", "1"),
              Pair("Number of Google Accounts", "1"),
              Pair("Sign-in Status", "Signed In"))));
  ShowAndCompleteSigninBubbleWithResult(account_info,
                                        SigninInterceptionResult::kAccepted);
  EXPECT_TRUE(IsChromeSignedIn());
}

// Tests that a HaTS survey is launched when users decline the intercept signin
// promo.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorWithHatsSurveyBrowserTest,
                       ShowHatsSurveyOnChromeSigninInterceptDeclined) {
  // Setup account for interception.
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());

  // Makes sure Chrome is not signed in to trigger the bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  EXPECT_CALL(
      *mock_hats_service(),
      LaunchDelayedSurvey(
          kHatsSurveyTriggerIdentityDiceWebSigninDeclined, _, _,
          UnorderedElementsAre(
              Pair("Channel", _),
              Pair("Chrome Version", version_info::GetVersion().GetString()),
              Pair("Number of Chrome Profiles", "1"),
              Pair("Number of Google Accounts", "1"),
              Pair("Sign-in Status", "Web Only Signed In"))));
  ShowAndCompleteSigninBubbleWithResult(account_info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_FALSE(IsChromeSignedIn());
}

// Test to sign in to Chrome from the Chrome Signin Bubble Intercept.
class DiceWebSigninInterceptorSigninBubbleBrowserTest
    : public DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest {
 public:
  // This function is specific to ChromeSigninDecline reprompt logic, as it does
  // not really advance time, but marks the prefs of interest in the past in
  // order to satisfy the `delta` given.
  void SimulateChromeSigninDeclinedAdvanceTime(const GaiaId& gaia,
                                               base::TimeDelta delta) {
    SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
    std::optional<base::Time> last_bubble_decline_time =
        signin_prefs.GetChromeSigninInterceptionLastBubbleDeclineTime(gaia);
    if (last_bubble_decline_time.has_value()) {
      signin_prefs.SetChromeSigninInterceptionLastBubbleDeclineTime(
          gaia, last_bubble_decline_time.value() - delta);
    }
  }

  base::TimeDelta time_since_last_reprompt(const GaiaId& gaia) {
    return DiceWebSigninInterceptor::
        GetTimeSinceLastChromeSigninDeclineForTesting(
            SigninPrefs(*GetProfile()->GetPrefs()), gaia);
  }

  // Simulate setting the ChromeSigninUserChoice through settings explicitly to
  // Do not signin.
  void SimulateSettingExplicitChromeSigninUserChoiceToDoNotSignin(
      std::string_view email) {
    settings::PeopleHandler handler(browser()->GetProfile());
    // The only for the value to take effect is to choose another one first.
    // Choose always ask first in case the value is already set to
    // `ChromeSigninUserChoice::kDoNotSignin`.
    handler.HandleSetChromeSigninUserChoiceForTesting(
        email, ChromeSigninUserChoice::kAlwaysAsk);
    handler.HandleSetChromeSigninUserChoiceForTesting(
        email, ChromeSigninUserChoice::kDoNotSignin);
  }
};

IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       ChromeSigninInterceptAccepted) {
  base::HistogramTester histogram_tester;
  base::UserActionTester user_action_tester;

  // Setup account for interception.
  const std::string account_email = "alice@example.com";
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      account_email, /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  ShowAndCompleteSigninBubbleWithResult(account_info,
                                        SigninInterceptionResult::kAccepted);

  EXPECT_TRUE(IsChromeSignedIn());

  // Check that the password account storage is enabled.
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(GetProfile());
  EXPECT_TRUE(
      password_manager::features_util::IsAccountStorageActive(sync_service));

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptChromeSignin);
  auto access_point = signin_metrics::AccessPoint::kChromeSigninInterceptBubble;
  histogram_tester.ExpectUniqueSample("Signin.SignIn.Started", access_point, 1);
  histogram_tester.ExpectUniqueSample("Signin.SignIn.Started.Profile1",
                                      access_point, 1);
  histogram_tester.ExpectUniqueSample("Signin.SignIn.Completed", access_point,
                                      1);
  histogram_tester.ExpectUniqueSample(
      "Signin.Intercept.ChromeSignin.DismissesBeforeAccept", 0, 1);

  ChromeSigninUserChoice user_choice =
      GetChromeSigninUserChoicePref(account_info);
  // User choice is remembered.
  EXPECT_EQ(user_choice, ChromeSigninUserChoice::kSignin);

  // Attempting to show the bubble after an explicit choice.

  // Signout to attempt signing in again and show the bubble.
  Signout();
  ASSERT_FALSE(IsChromeSignedIn());
  // Make account available again.
  account_info = MakeAccountInfoAvailableAndUpdate(
      account_email, /*hosted_domain=*/std::string());
  // Chrome Signin bubble should not show if the user already made a choice.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(account_info);
}

class DiceWebSigninInterceptorSigninBubbleWithAccountPreviewBrowserTest
    : public DiceWebSigninInterceptorSigninBubbleBrowserTest {
 public:
  DiceWebSigninInterceptorSigninBubbleWithAccountPreviewBrowserTest() {
    feature_list_.InitAndEnableFeature(
        switches::kEnableAccountPreviewPreferredAccount);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
};

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorSigninBubbleWithAccountPreviewBrowserTest,
    ChromeSigninInterceptWithAccountPreviewPreference) {
  // Setup account for interception.
  const std::string account_email = "alice@example.com";
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      account_email, /*hosted_domain=*/std::string());
  ASSERT_FALSE(IsChromeSignedIn());

  signin::AccountPreviewDataService::AccountPreviewPreference pref;
  pref.preferred_data_types.push_back(
      {syncer::BOOKMARKS, signin::SyncDataQuartile::kAboveQ3});
  pref.other_device_form_factor =
      sync_pb::SyncEnums_DeviceFormFactor_DEVICE_FORM_FACTOR_PHONE;
  GetTestAccountPreviewDataService(GetProfile())->SetPreviewPreference(pref);

  FakeDiceWebSigninInterceptorDelegate* delegate =
      ShowSigninBubble(account_info, /*expected_result=*/std::nullopt);

  EXPECT_TRUE(delegate->intercept_bubble_shown());
  EXPECT_THAT(
      delegate->last_bubble_parameters(),
      testing::Optional(testing::AllOf(
          testing::Field(
              &DiceWebSigninInterceptorDelegate::BubbleParameters::
                  interception_type,
              WebSigninInterceptor::SigninInterceptionType::kChromeSignin),
          testing::Field(&DiceWebSigninInterceptorDelegate::BubbleParameters::
                             account_preview_preference,
                         pref))));
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorSigninBubbleWithAccountPreviewBrowserTest,
    MultiUserSigninInterceptWithAccountPreviewPreference) {
  // Set up for Multi user signin interception.
  AccountInfo primary_account_info =
      identity_test_env()->MakePrimaryAccountAvailable(
          "bob@example.com", signin::ConsentLevel::kSignin);
  AccountInfo secondary_account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());

  signin::AccountPreviewDataService::AccountPreviewPreference pref;
  pref.preferred_data_types.push_back(
      {syncer::BOOKMARKS, signin::SyncDataQuartile::kAboveQ3});
  pref.other_device_form_factor =
      sync_pb::SyncEnums_DeviceFormFactor_DEVICE_FORM_FACTOR_PHONE;
  GetTestAccountPreviewDataService(GetProfile())->SetPreviewPreference(pref);

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Intercept.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kMultiUser);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kAccepted);
  ProfileWaiter waiter;
  interceptor->MaybeInterceptWebSignin(
      web_contents, secondary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(
      secondary_account_info.GetAccountId(), {});

  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  EXPECT_THAT(
      source_interceptor_delegate->last_bubble_parameters(),
      testing::Optional(testing::AllOf(
          testing::Field(
              &DiceWebSigninInterceptorDelegate::BubbleParameters::
                  interception_type,
              WebSigninInterceptor::SigninInterceptionType::kMultiUser),
          testing::Field(&DiceWebSigninInterceptorDelegate::BubbleParameters::
                             account_preview_preference,
                         pref))));

  waiter.WaitForProfileAdded();
}

IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       ChromeSigninInterceptDeclined) {
  base::HistogramTester histogram_tester;
  base::UserActionTester user_action_tester;

  // Setup account for interception.
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(signin_prefs
                   .GetChromeSigninInterceptionLastBubbleDeclineTime(
                       account_info.GetGaiaId())
                   .has_value());

  ShowAndCompleteSigninBubbleWithResult(account_info,
                                        SigninInterceptionResult::kDeclined);

  EXPECT_FALSE(IsChromeSignedIn());
  EXPECT_FALSE(password_manager::features_util::IsAccountStorageActive(
      SyncServiceFactory::GetForProfile(GetProfile())));

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptChromeSignin);
  auto access_point = signin_metrics::AccessPoint::kChromeSigninInterceptBubble;
  histogram_tester.ExpectUniqueSample("Signin.SignIn.Started", access_point, 0);
  histogram_tester.ExpectUniqueSample("Signin.SignIn.Completed", access_point,
                                      0);

  // User choice is remembered and decline time is stored.
  EXPECT_EQ(GetChromeSigninUserChoicePref(account_info),
            ChromeSigninUserChoice::kDoNotSignin);
  // Bubble decline time set.
  EXPECT_TRUE(signin_prefs
                  .GetChromeSigninInterceptionLastBubbleDeclineTime(
                      account_info.GetGaiaId())
                  .has_value());
  // But no reprompt count.
  EXPECT_EQ(
      signin_prefs.GetChromeSigninBubbleRepromptCount(account_info.GetGaiaId()),
      0);

  histogram_tester.ExpectUniqueSample(
      "Signin.Intercept.ChromeSignin.DismissesBeforeDecline", 0, 1);

  // Attempting to show the bubble after an explicit choice.
  // Not enough time for a reprompt yet, bubble is not shown.

  ASSERT_FALSE(IsChromeSignedIn());
  // Chrome Signin bubble should not show if the user already made a choice.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(account_info);
}

// In this test, we simulate moving time forward by setting the needed pref in
// the past. This allows to have the right conditions for reprompts. Testing the
// minimum time reprompt logic here.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       ChromeSigninInterceptDeclinesAndReprompts) {
  base::HistogramTester histogram_tester;
  // Setup account for interception.
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com",
                                        /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());
  ASSERT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_FALSE(IsChromeSignedIn());
  // Decline time pref is set.
  std::optional<base::Time> initial_decline_time =
      signin_prefs.GetChromeSigninInterceptionLastBubbleDeclineTime(
          info.GetGaiaId());
  ASSERT_TRUE(initial_decline_time.has_value());
  // Reprompt count is 0.
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline", 0);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.RepromptCount", 0);

  // Immediate attempt to show the bubble should not succeed, since not enough
  // time has passed.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(15));

  // Attempt before the minimum duration for reprompt has passed, it should
  // fail.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(46));

  // Bubble should show as we are in the first period where the bubble can be
  // reprompted. Decline it to proceed with the reprompts.
  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  // Last bubble time pref is still set.
  std::optional<base::Time> updated_last_decline_time =
      signin_prefs.GetChromeSigninInterceptionLastBubbleDeclineTime(
          info.GetGaiaId());
  ASSERT_TRUE(updated_last_decline_time.has_value());
  // And different from the initial decline time.
  EXPECT_NE(initial_decline_time.value(), updated_last_decline_time.value());
  // Reprompt count updated
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            1);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline", 1);
  histogram_tester.ExpectUniqueSample(
      "Signin.Intercept.ChromeSignin.RepromptCount", 1, 1);

  // Move time forward with less time than the expected minimum duration for the
  // reprompt. Should not show the bubble again yet.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(31));

  ASSERT_LT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  // Move time forward enough to bypass the minimum duration for the reprompt.
  // Should show the bubble again now.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(41));

  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  // Decline it again to keep trying later. Second reprompt decline total
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            2);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline", 2);
  histogram_tester.ExpectBucketCount(
      "Signin.Intercept.ChromeSignin.RepromptCount", 2, 1);

  // Move time forward enough time to bypass the minimum reprompt duration by a
  // big margin.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(120));

  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  // Decline it again to keep trying later. 3rd reprompt decline.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            3);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline", 3);
  histogram_tester.ExpectBucketCount(
      "Signin.Intercept.ChromeSignin.RepromptCount", 3, 1);

  // Repeat same operation for the last allowed reprompt.

  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(120));
  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            4);
  histogram_tester.ExpectTotalCount(
      "Signin.Intercept.ChromeSignin.NumberOfDaysSinceLastDecline", 4);
  histogram_tester.ExpectBucketCount(
      "Signin.Intercept.ChromeSignin.RepromptCount", 4, 1);

  // Maximum reprompt count reached. Make sure that no reprompts will be made
  // regardless of the time that has passed.

  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(30));
  // Less than the minimum duration between reprompts.
  ASSERT_LT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(120));
  // More than the minimum duration between reprompts.
  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  // Still no reprompt.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);
}

// This test makes sure that the reprompts are count based and not depending one
// total time duration.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       ChromeSigninInterceptRepromptsHasNoTimeLimit) {
  // Setup account for interception.
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com",
                                        /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);

  EXPECT_TRUE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());

  // Advance a large amount of time. Greater than the minimum duration.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(300));

  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  // Reprompt should happen.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);

  // Advance even larger amount of time.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(300));

  // Larger than the minimum duration.
  ASSERT_GT(time_since_last_reprompt(info.GetGaiaId()), base::Days(60));
  // Reprompt should happen as the max count was not reached yet. The amount of
  // time that has passed is not significant as long as it is more than the
  // minimum duration between reprompts.
  // Result is not important.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kAccepted);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorSigninBubbleBrowserTest,
    ChromeSigninInterceptDeclinesRepromptAttemptWithExplicitDoNotSignin) {
  // Setup account for interception.
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com",
                                        /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);

  EXPECT_TRUE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());

  // Simulates settings change by the user through the settings page.
  SimulateSettingExplicitChromeSigninUserChoiceToDoNotSignin(info.GetEmail());

  EXPECT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);

  // Advance a large amount of time. No reprompt is expected.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(100));

  // No reprompts since the choice was explicitly set through settings.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorSigninBubbleBrowserTest,
    ChromeSigninInterceptDeclinesRepromptsThenDismissReprompt) {
  // Setup account for interception.
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com",
                                        /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  // Start by dismissing the bubble 3 times, to set up for later.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  EXPECT_TRUE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());

  // Advance enough time for a reprompt.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(70));

  ASSERT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);
  // Reprompt should be successful and we dismiss it.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  // Reprompt count did not change, as the dismiss did not trigger a completed
  // reprompt. Only decline should do that.
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);

  // A followup reprompt is then allowed directly without more time passing.
  // Dismissing again, the 5th time (given the first 3 dismisses), should be
  // treated as a decline and update the the reprompt count.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            1);

  // Followup attempt to show the bubble should fail, without increasing the
  // time.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  // Finally increasing the time should allow for more reprompts as we did not
  // reach the limit yet.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(70));

  // And followup dismisses should directly be treated as declines still.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            2);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorSigninBubbleBrowserTest,
    ChromeSigninInterceptDeclinesRepromptsThenAcceptReprompt) {
  // Setup account for interception.
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com",
                                        /*hosted_domain=*/std::string());
  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  SigninPrefs signin_prefs(*GetProfile()->GetPrefs());
  ASSERT_FALSE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  // Bubble last decline time is set.
  EXPECT_TRUE(
      signin_prefs
          .GetChromeSigninInterceptionLastBubbleDeclineTime(info.GetGaiaId())
          .has_value());
  // Choice is set impliclty.
  EXPECT_EQ(
      signin_prefs.GetChromeSigninInterceptionUserChoice(info.GetGaiaId()),
      ChromeSigninUserChoice::kDoNotSignin);
  // No reprompt yet.
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);

  // Advance enough time for a reprompt.
  SimulateChromeSigninDeclinedAdvanceTime(info.GetGaiaId(), base::Days(70));

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kAccepted);
  // Implicit choice is overridden to always sign in, accepting the bubble.
  EXPECT_EQ(
      signin_prefs.GetChromeSigninInterceptionUserChoice(info.GetGaiaId()),
      ChromeSigninUserChoice::kSignin);
  // Still no reprompt.
  EXPECT_EQ(signin_prefs.GetChromeSigninBubbleRepromptCount(info.GetGaiaId()),
            0);
  EXPECT_TRUE(IsChromeSignedIn());
}

// Test the memory of the user's account storage preference.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       DisableAccountStorage) {
  // Setup account and accept intersection.
  const std::string email("alice@example.com");
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate(email, /*hosted_domain=*/std::string());
  ShowAndCompleteSigninBubbleWithResult(account_info,
                                        SigninInterceptionResult::kAccepted);

  // Check that the password account storage is enabled.
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForProfile(GetProfile());
  EXPECT_TRUE(
      password_manager::features_util::IsAccountStorageActive(sync_service));

  // Disable account storage.
  sync_service->GetUserSettings()->SetSelectedType(
      syncer::UserSelectableType::kPasswords, false);

  // Check that the password account storage is disabled.
  EXPECT_FALSE(
      password_manager::features_util::IsAccountStorageActive(sync_service));

  Signout();

  // Check that the password account storage is false if there is no account.
  EXPECT_FALSE(
      password_manager::features_util::IsAccountStorageActive(sync_service));

  // Log in again.
  // Force a Chrome Signin. The bubble will not be shown again.
  identity_test_env()->MakePrimaryAccountAvailable(
      email, signin::ConsentLevel::kSignin);

  // Check that the password account storage is still disabled.
  EXPECT_FALSE(
      password_manager::features_util::IsAccountStorageActive(sync_service));
}

// Test the recording of the user entering or resolving an inconsistent state
// (sign in pending with account A, sign in to web with account B).)
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       RecordInconsistentStateResolvedAfterSignInPending) {
  base::HistogramTester histogram_tester;

  // Set up a primary account in sign in pending state and a secondary account
  // signing into the web, therefore inducing an inconsistent state.
  AccountInfo primary_account_info =
      identity_test_env()->MakePrimaryAccountAvailable(
          "bob@example.com", signin::ConsentLevel::kSignin);
  identity_test_env()->SetInvalidRefreshTokenForPrimaryAccount();
  AccountInfo secondary_account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Intercept.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kMultiUser);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kDismissed);
  interceptor->MaybeInterceptWebSignin(
      web_contents, secondary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/false,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);

  histogram_tester.ExpectBucketCount(
      "Signin.SigninPending.InconsistentStateInvoked", true, 1);
  histogram_tester.ExpectBucketCount(
      "Signin.SigninPending.InconsistentStateInvoked.Profile1", true, 1);
}

IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorSigninBubbleBrowserTest,
                       MultiUserSigninInterception) {
  // Set up for Multi user signin interception.
  AccountInfo primary_account_info =
      identity_test_env()->MakePrimaryAccountAvailable(
          "bob@example.com", signin::ConsentLevel::kSignin);
  AccountInfo secondary_account_info = MakeAccountInfoAvailableAndUpdate(
      "alice@example.com", /*hosted_domain=*/std::string());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Intercept.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kMultiUser);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kAccepted);
  ProfileWaiter waiter;
  interceptor->MaybeInterceptWebSignin(
      web_contents, secondary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(
      secondary_account_info.GetAccountId(), {});

  // New Profile created from accepting the signin interception.
  Profile* new_profile = waiter.WaitForProfileAdded();
  // Should be signed in.
  EXPECT_TRUE(IdentityManagerFactory::GetForProfile(new_profile)
                  ->HasPrimaryAccount(signin::ConsentLevel::kSignin));
  // ChromeSignin setting should be set.
  EXPECT_EQ(SigninPrefs(*new_profile->GetPrefs())
                .GetChromeSigninInterceptionUserChoice(
                    secondary_account_info.GetGaiaId()),
            ChromeSigninUserChoice::kSignin);
}

// This test mainly checks the combination of dismissal and the effect it has on
// the user choice. Simulating multiple accounts and checks that they do not
// affect each other:
// - Account1 dismisses the bubble twice.
// - Account2 dismisses the bubble once.
// - Account1 dismisses the bubble three more times, and make sure it is a
// decline.
// - Account2 accept the bubble.
// - Account1 changes it's pref to always ask and should show the bubble even
// after 5 dismisses.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest,
    ChromeSigninInterceptDismissBehavior) {
  base::HistogramTester histogram_tester;

  // Setup a first account for interception.
  const std::string email1("alice1@example.com");
  AccountInfo info1 = MakeAccountInfoAvailableAndUpdate(
      email1, /*hosted_domain=*/std::string());
  ASSERT_EQ(GetChromeSigninInterceptDismissCountPref(info1), 0);
  ASSERT_EQ(GetChromeSigninUserChoicePref(info1),
            ChromeSigninUserChoice::kNoChoice);

  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  // Intercept declined on account1 twice.
  ShowAndCompleteSigninBubbleWithResult(info1,
                                        SigninInterceptionResult::kDismissed);
  int expected_dismiss_count = 1;
  EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info1),
            expected_dismiss_count);
  EXPECT_EQ(GetChromeSigninUserChoicePref(info1),
            ChromeSigninUserChoice::kNoChoice);

  ShowAndCompleteSigninBubbleWithResult(info1,
                                        SigninInterceptionResult::kDismissed);
  ++expected_dismiss_count;
  EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info1),
            expected_dismiss_count);

  // Setup the second account for interception.
  AccountInfo info2 =
      MakeAccountInfoAvailableAndUpdate("alice2@example.com",
                                        /*hosted_domain=*/std::string());
  ASSERT_FALSE(info2.IsEmpty());
  ASSERT_EQ(GetChromeSigninInterceptDismissCountPref(info2), 0);

  // Intercept dismissed on account2.
  ShowAndCompleteSigninBubbleWithResult(info2,
                                        SigninInterceptionResult::kDismissed);

  // Account2 pref should be affected and account1 should not.
  EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info1),
            expected_dismiss_count);
  EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info2), 1);

  // 3 more dismisses on account1:
  for (int i = 0; i < 3; ++i) {
    ShowAndCompleteSigninBubbleWithResult(info1,
                                          SigninInterceptionResult::kDismissed);
    ++expected_dismiss_count;
    EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info1),
              expected_dismiss_count);
  }
  // 5 dismiss treated as a do not sign in.
  EXPECT_EQ(GetChromeSigninUserChoicePref(info1),
            ChromeSigninUserChoice::kDoNotSignin);

  // A decline should have been recorded.
  size_t expected_decline_count = 1;
  histogram_tester.ExpectBucketCount(
      "Signin.Intercept.ChromeSignin.DismissesBeforeDecline",
      /*sample=*/5, /*expected_count=*/expected_decline_count);

  // A 6h attempt to show should fail.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info1);

  // Accepting the intercept on account2 should reset the pref and log in the
  // histogram.
  ShowAndCompleteSigninBubbleWithResult(info2,
                                        SigninInterceptionResult::kAccepted);
  // Dismiss count remains.
  EXPECT_EQ(GetChromeSigninInterceptDismissCountPref(info2), 1);
  // Record the 1 dismiss that happened before accepting the intercept.
  histogram_tester.ExpectUniqueSample(
      "Signin.Intercept.ChromeSignin.DismissesBeforeAccept",
      /*sample=*/1, /*expected_bucket_count=*/1);

  // Make sure to signout account2.
  Signout();
  // Make account1 available again.
  info1 = MakeAccountInfoAvailableAndUpdate(email1,
                                            /*hosted_domain=*/std::string());
  // Override account1 pref to always ask.
  SigninPrefs(*GetProfile()->GetPrefs())
      .SetChromeSigninInterceptionUserChoice(
          info1.GetGaiaId(), ChromeSigninUserChoice::kAlwaysAsk);
  // Showing the bubble should succeed -- result is not important, only affect
  // histogram recorded.
  ShowAndCompleteSigninBubbleWithResult(info1,
                                        SigninInterceptionResult::kDeclined);
  ++expected_decline_count;
  histogram_tester.ExpectBucketCount(
      "Signin.Intercept.ChromeSignin.DismissesBeforeDecline",
      /*sample=*/5, /*expected_count=*/expected_decline_count);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest,
    OverrideUserChoicePrefAfterAccept) {
  // Setup an account for interception.
  const std::string email("alice1@example.com");
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate(email, /*hosted_domain=*/std::string());

  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kAccepted);
  // Choice is remembered.
  EXPECT_EQ(GetChromeSigninUserChoicePref(info),
            ChromeSigninUserChoice::kSignin);

  // Signout to attempt signing in again.
  Signout();
  // Make account available again.
  info =
      MakeAccountInfoAvailableAndUpdate(email, /*hosted_domain=*/std::string());
  // Attempting to show the bubble again should fail since we already have a
  // user choice.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  // Override account1 pref to always ask -- simulating changing it through the
  // settings.
  SigninPrefs(*GetProfile()->GetPrefs())
      .SetChromeSigninInterceptionUserChoice(
          info.GetGaiaId(), ChromeSigninUserChoice::kAlwaysAsk);
  // Showing the bubble should succeed -- result is not important.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest,
    OverrideUserChoicePrefAfterDecline) {
  // Setup an account for interception.
  const std::string email("alice1@example.com");
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate(email, /*hosted_domain=*/std::string());

  // Makes sure Chrome is not signed in to trigger the Chrome Signin intercept
  // bubble.
  ASSERT_FALSE(IsChromeSignedIn());

  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  // Choice is remembered.
  EXPECT_EQ(GetChromeSigninUserChoicePref(info),
            ChromeSigninUserChoice::kDoNotSignin);

  // Attempting to show the bubble again should fail since we already have a
  // user choice.
  ExpectAttemptToShowChromeSigninBubbleNotToShow(info);

  // Override account1 pref to always ask -- simulating changing it through the
  // settings.
  SigninPrefs(*GetProfile()->GetPrefs())
      .SetChromeSigninInterceptionUserChoice(
          info.GetGaiaId(), ChromeSigninUserChoice::kAlwaysAsk);
  // Showing the bubble should succeed -- result is not important.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
}

IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest,
    ChromeSigninBubbleResultsWithAlwaysAskUserChoice) {
  // Setup an account for interception.
  const std::string email("alice1@example.com");
  AccountInfo info =
      MakeAccountInfoAvailableAndUpdate(email, /*hosted_domain=*/std::string());

  // Set user choice to `ChromeSigninUserChoice::kAlwaysAsk` mode.
  SigninPrefs(*GetProfile()->GetPrefs())
      .SetChromeSigninInterceptionUserChoice(
          info.GetGaiaId(), ChromeSigninUserChoice::kAlwaysAsk);

  int current_dismiss_count = GetChromeSigninInterceptDismissCountPref(info);

  // Dismiss action.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDismissed);
  // Should not alter the dismiss count when in
  // `ChromeSigninUserChoice::kAlwaysAsk` mode.
  EXPECT_EQ(current_dismiss_count,
            GetChromeSigninInterceptDismissCountPref(info));

  // Decline action.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kDeclined);
  // Choice should not be remembered when in
  // `ChromeSigninUserChoice::kAlwaysAsk` mode.
  EXPECT_EQ(GetChromeSigninUserChoicePref(info),
            ChromeSigninUserChoice::kAlwaysAsk);

  // Accept action.
  ShowAndCompleteSigninBubbleWithResult(info,
                                        SigninInterceptionResult::kAccepted);
  // Choice should not be remembered when in
  // `ChromeSigninUserChoice::kAlwaysAsk` mode.
  EXPECT_EQ(GetChromeSigninUserChoicePref(info),
            ChromeSigninUserChoice::kAlwaysAsk);
}

// Test fixture for the parameterized `SigninNotAllowedByPattern` test.
struct SigninNotAllowedByPatternTestParams {
  const char* const email;
  const bool expect_bubble_shown;
};

class DiceWebSigninInterceptorSigninNotAllowedByPatternBrowserTest
    : public DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest,
      public testing::WithParamInterface<SigninNotAllowedByPatternTestParams> {
 public:
  void SetUpOnMainThread() override {
    DiceWebSigninInterceptorWithChromeSigninHelpersBrowserTest::
        SetUpOnMainThread();
    // Set the signin pattern to allow only @example.com accounts.
    g_browser_process->local_state()->SetString(
        prefs::kGoogleServicesUsernamePattern, ".*@example.com");
  }

 private:
  base::test::ScopedFeatureList feature_list_{
      syncer::kReplaceSyncPromosWithSignInPromos};
};

IN_PROC_BROWSER_TEST_P(
    DiceWebSigninInterceptorSigninNotAllowedByPatternBrowserTest,
    SigninNotAllowedByPattern) {
  const SigninNotAllowedByPatternTestParams& params = GetParam();

  // Create an account with the email from the test parameters.
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      params.email, /*hosted_domain=*/std::string());
  FakeDiceWebSigninInterceptorDelegate* delegate =
      ShowSigninBubble(account_info, /*expected_result=*/std::nullopt);

  EXPECT_EQ(delegate->intercept_bubble_shown(), params.expect_bubble_shown);
}

IN_PROC_BROWSER_TEST_P(
    DiceWebSigninInterceptorSigninNotAllowedByPatternBrowserTest,
    ForcedEnterpriseInterception) {
  const SigninNotAllowedByPatternTestParams& params = GetParam();
  signin::IdentityManager* identity_manager =
      identity_test_env()->identity_manager();

  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate(params.email, "example.com");
  signin::SetCookieAccounts(
      identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Do the signin interception.
  FakeDiceWebSigninInterceptorDelegate* interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  ProfileWaiter profile_waiter;
  base::RunLoop token_removed_run_loop;
  signin::TestIdentityManagerObserver token_updated_observer(identity_manager);
  token_updated_observer.SetOnRefreshTokenRemovedCallback(
      token_removed_run_loop.QuitClosure());
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  if (params.expect_bubble_shown) {
    // Wait for the interception to be complete.
    profile_waiter.WaitForProfileAdded();
  }

  EXPECT_EQ(interceptor_delegate->intercept_bubble_shown(),
            params.expect_bubble_shown);
  EXPECT_EQ(interceptor_delegate->signin_error(),
            params.expect_bubble_shown
                ? std::optional<SigninUIError>()
                : SigninUIError::UsernameNotAllowedByPatternFromPrefs(
                      account_info.GetEmail()));
  EXPECT_FALSE(interceptor->is_interception_in_progress());
  // If the interception happened, the account was moved to another profile.
  // Otherwise the account was removed entirely.
  token_removed_run_loop.Run();
  EXPECT_TRUE(identity_manager->GetAccountsWithRefreshTokens().empty());
}

IN_PROC_BROWSER_TEST_P(
    DiceWebSigninInterceptorSigninNotAllowedByPatternBrowserTest,
    OptionalEnterpriseInterception) {
  const SigninNotAllowedByPatternTestParams& params = GetParam();
  signin::IdentityManager* identity_manager =
      identity_test_env()->identity_manager();

  AccountInfo primary_account_info =
      MakeAccountInfoAvailableAndUpdate("bob@example.com", "example.com");
  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(primary_account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);
  enterprise_util::SetUserAcceptedAccountManagement(GetProfile(), true);

  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate(params.email, "example.com");
  signin::SetCookieAccounts(
      identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});

  // Do not enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies(""));
  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Do the signin interception.
  FakeDiceWebSigninInterceptorDelegate* interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
  ProfileWaiter profile_waiter;
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(
          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  if (params.expect_bubble_shown) {
    // Wait for the interception to be complete.
    profile_waiter.WaitForProfileAdded();
  }

  EXPECT_EQ(interceptor_delegate->intercept_bubble_shown(),
            params.expect_bubble_shown);
  EXPECT_EQ(interceptor_delegate->signin_error(), std::nullopt);
  EXPECT_FALSE(interceptor->is_interception_in_progress());
  // If the interception happened, the account was moved to another profile.
  if (params.expect_bubble_shown) {
    EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(), 1u);
  } else {
    // Otherwise nothing happened.
    EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(), 2u);
  }
  EXPECT_EQ(
      identity_manager->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
      primary_account_info.GetAccountId());
}

INSTANTIATE_TEST_SUITE_P(
    All,
    DiceWebSigninInterceptorSigninNotAllowedByPatternBrowserTest,
    testing::Values(SigninNotAllowedByPatternTestParams{"test@nope.com", false},
                    SigninNotAllowedByPatternTestParams{"test@example.com",
                                                        true}));

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       ForcedEnterpriseInterceptionTestNoForcedInterception) {
  base::HistogramTester histogram_tester;

  AccountInfo primary_account_info =
      MakeAccountInfoAvailableAndUpdate("bob@example.com", "example.com");
  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(primary_account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);

  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies(""));

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(new_profile));
  ASSERT_TRUE(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
      GetInterceptorDelegate(new_profile);
  new_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterprise);

  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);

  // Check the profile name.
  ProfileAttributesStorage& storage =
      g_browser_process->profile_manager()->GetProfileAttributesStorage();
  ProfileAttributesEntry* entry =
      storage.GetProfileAttributesWithPath(new_profile->GetPath());
  ASSERT_TRUE(entry);
  EXPECT_EQ("example.com", base::UTF16ToUTF8(entry->GetLocalProfileName()));
  // Check the profile color.
  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
                  ->GetUserColor()
                  .has_value());

  // A browser has been created for the new profile and the tab was moved there.
  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
  ASSERT_TRUE(added_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(
      added_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptEnterprise);

  // First run experience UI was shown exactly once in the new profile.
  EXPECT_EQ(new_interceptor_delegate->fre_browser(), added_browser);
  EXPECT_EQ(new_interceptor_delegate->fre_account_id(),
            account_info.GetAccountId());
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       EnterpriseInterceptionDeclined) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  AccountInfo primary_account_info =
      MakeAccountInfoAvailableAndUpdate("bob@example.com", "example.com");

  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(primary_account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterprise);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kDeclined);

  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(GetProfile());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptEnterprise);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionWhileManagementDisclaimerActive) {
  auto* profile_management_disclaimer_service =
      ProfileManagementDisclaimerServiceFactory::GetForProfile(GetProfile());
  base::HistogramTester histogram_tester;
  AccountInfo primary_account_info =
      MakeAccountInfoAvailableAndUpdate("bob@example.com", "example.com");

  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(primary_account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);
  profile_management_disclaimer_service->EnsureManagedProfileForAccount(
      primary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin, base::DoNothing());
  SetupGaiaResponses();
  ASSERT_EQ(profile_management_disclaimer_service
                ->GetAccountBeingConsideredForManagementIfAny(),
            primary_account_info.GetAccountId());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Start the management disclaimer.
  profile_management_disclaimer_service->EnsureManagedProfileForAccount(
      primary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin, base::DoNothing());

  ASSERT_EQ(profile_management_disclaimer_service
                ->GetAccountBeingConsideredForManagementIfAny(),
            primary_account_info.GetAccountId());

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);

  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());

  // Enforce enterprise profile separation.
  interceptor->SetInterceptedAccountProfileSeparationPoliciesForTesting(
      policy::ProfileSeparationPolicies(
          policy::ProfileSeparationSettings::ENFORCED, std::nullopt));

  interceptor->MaybeInterceptWebSignin(
      web_contents, primary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/false,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  ASSERT_EQ(profile_management_disclaimer_service
                ->GetAccountBeingConsideredForManagementIfAny(),
            primary_account_info.GetAccountId());
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(GetProfile());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  EXPECT_FALSE(
      GetInterceptorDelegate(GetProfile())->intercept_bubble_destroyed());
  EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(
      primary_account_info.GetAccountId()));
  EXPECT_TRUE(
      identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kAbortDisclaimerServiceInProgress);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       ForcedEnterpriseInterceptionReauthDeclined) {
  base::HistogramTester histogram_tester;
  AccountInfo primary_account_info =
      MakeAccountInfoAvailableAndUpdate("bob@example.com", "example.com");

  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(primary_account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);
  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kDeclined);

  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());

  // Enforce enterprise profile separation.
  interceptor->SetInterceptedAccountProfileSeparationPoliciesForTesting(
      policy::ProfileSeparationPolicies(
          policy::ProfileSeparationSettings::ENFORCED, std::nullopt));

  interceptor->MaybeInterceptWebSignin(
      web_contents, primary_account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/false,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(GetProfile());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      primary_account_info.GetAccountId()));
  EXPECT_FALSE(
      identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       ForcedEnterpriseInterceptionTestAccountLevelPolicy) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies("primary_account"));

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  EXPECT_TRUE(enterprise_util::UserAcceptedAccountManagement(new_profile));
  ASSERT_TRUE(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
      GetInterceptorDelegate(new_profile);
  new_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);

  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);

  // Check the profile name.
  ProfileAttributesStorage& storage =
      g_browser_process->profile_manager()->GetProfileAttributesStorage();
  ProfileAttributesEntry* entry =
      storage.GetProfileAttributesWithPath(new_profile->GetPath());
  ASSERT_TRUE(entry);
  EXPECT_EQ("example.com", base::UTF16ToUTF8(entry->GetLocalProfileName()));
  // Check the profile color.
  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
                  ->GetUserColor()
                  .has_value());

  // A browser has been created for the new profile and the tab was moved there.
  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
  ASSERT_TRUE(added_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(
      added_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);

  // First run experience UI was shown exactly once in the new profile.
  EXPECT_EQ(new_interceptor_delegate->fre_browser(), added_browser);
  EXPECT_EQ(new_interceptor_delegate->fre_account_id(),
            account_info.GetAccountId());
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionTestAccountLevelPolicyMergeData) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies("primary_account"));

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kAcceptedWithExistingProfile);

  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  // Wait for the interception to be complete.
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  EXPECT_TRUE(identity_manager()->HasAccountWithRefreshToken(
      account_info.GetAccountId()));
  EXPECT_EQ(
      identity_manager()->GetPrimaryAccountId(signin::ConsentLevel::kSignin),
      account_info.GetAccountId());
  EXPECT_TRUE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionTestAccountLevelPolicyDeclined) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies("primary_account"));

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kDeclined);

  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(GetProfile());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionTestAccountLevelPolicyStrictDeclined) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "none");
  DiceWebSigninInterceptorFactory::GetForProfile(GetProfile())
      ->SetInterceptedAccountProfileSeparationPoliciesForTesting(
          policy::ProfileSeparationPolicies("primary_account_strict"));

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  source_interceptor_delegate->set_expected_interception_result(
      SigninInterceptionResult::kDeclined);

  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop run_loop;
  run_loop.RunUntilIdle();

  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForProfile(GetProfile());
  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       ForcedEnterpriseInterceptionTest) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  EXPECT_TRUE(enterprise_util::UserAcceptedAccountManagement(new_profile));
  ASSERT_TRUE(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
      GetInterceptorDelegate(new_profile);
  new_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);

  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);

  // Check the profile name.
  ProfileAttributesStorage& storage =
      g_browser_process->profile_manager()->GetProfileAttributesStorage();
  ProfileAttributesEntry* entry =
      storage.GetProfileAttributesWithPath(new_profile->GetPath());
  ASSERT_TRUE(entry);
  EXPECT_EQ("example.com", base::UTF16ToUTF8(entry->GetLocalProfileName()));
  // Check the profile color.
  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
                  ->GetUserColor()
                  .has_value());

  // A browser has been created for the new profile and the tab was moved there.
  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
  ASSERT_TRUE(added_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(
      added_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);

  // First run experience UI was shown exactly once in the new profile.
  EXPECT_EQ(new_interceptor_delegate->fre_browser(), added_browser);
  EXPECT_EQ(new_interceptor_delegate->fre_account_id(),
            account_info.GetAccountId());
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// Tests the complete interception flow for a reauth of the primary account of a
// non-syncing profile.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionPrimaryAccountReauthSyncDisabledTest) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(account_info.GetAccountId(),
                          signin::ConsentLevel::kSignin,
                          signin_metrics::AccessPoint::kStartPage);

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kEnterpriseForced);

  EXPECT_FALSE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/false,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(enterprise_util::UserAcceptedAccountManagement(GetProfile()));
  // Interception bubble was closed.
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_TRUE(IdentityManagerFactory::GetForProfile(GetProfile())
                  ->HasAccountWithRefreshToken(account_info.GetAccountId()));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(
      histogram_tester,
      SigninInterceptionHeuristicOutcome::kInterceptEnterpriseForced);
}

// Tests the complete interception flow for a reauth of the primary account of a
// syncing profile.
IN_PROC_BROWSER_TEST_F(
    DiceWebSigninInterceptorBrowserTest,
    ForcedEnterpriseInterceptionPrimaryAccountReauthSyncEnabledTest) {
  base::HistogramTester histogram_tester;
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  IdentityManagerFactory::GetForProfile(GetProfile())
      ->GetPrimaryAccountMutator()
      ->SetPrimaryAccount(account_info.GetAccountId(),
                          signin::ConsentLevel::kSync,
                          signin_metrics::AccessPoint::kStartPage);

  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");

  SetupGaiaResponses();

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  enterprise_util::SetUserAcceptedAccountManagement(GetProfile(), true);
  // Start the interception.
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/false,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  base::RunLoop().RunUntilIdle();
  // Interception bubble was closed.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_shown());
  EXPECT_FALSE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_TRUE(IdentityManagerFactory::GetForProfile(GetProfile())
                  ->HasAccountWithRefreshToken(account_info.GetAccountId()));

  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count);
  EXPECT_EQ(
      browser()->GetTabStripModel()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kAbortAccountNotNew);
}

// Tests the complete profile switch flow when the profile is not loaded.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       EnterpriseSwitchAndLoad) {
  base::HistogramTester histogram_tester;
  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");

  // Add a profile in the cache (simulate the profile on disk).
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  ProfileAttributesStorage* profile_storage =
      &profile_manager->GetProfileAttributesStorage();
  const base::FilePath profile_path =
      profile_manager->GenerateNextProfileDirectoryPath();
  ProfileAttributesInitParams params;
  params.profile_path = profile_path;
  params.profile_name = u"TestProfileName";
  params.gaia_id = account_info.GetGaiaId();
  params.user_name = base::UTF8ToUTF16(account_info.GetEmail());
  profile_storage->AddProfile(std::move(params));
  ProfileAttributesEntry* entry =
      profile_storage->GetProfileAttributesWithPath(profile_path);
  ASSERT_TRUE(entry);
  ASSERT_EQ(entry->GetGAIAId(), account_info.GetGaiaId());

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  source_interceptor_delegate->set_expected_interception_type(
      WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  ASSERT_TRUE(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  // Check that the right profile was opened.
  EXPECT_EQ(new_profile->GetPath(), profile_path);

  // Add the account to the cookies (simulates the account reconcilor).
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  signin::SetCookieAccounts(
      new_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});
  const BrowserWindowInterface* const added_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(added_browser);

  // A browser has been created for the new profile and the tab was moved there.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(added_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::
                      kInterceptEnterpriseForcedProfileSwitch);

  // Interception bubble was closed.
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());

  // First run experience was not shown.
  EXPECT_EQ(GetInterceptorDelegate(new_profile)->fre_browser(), nullptr);
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// Tests the complete profile switch flow when the profile is already loaded.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest,
                       EnterpriseSwitchAlreadyOpen) {
  base::HistogramTester histogram_tester;
  // Enforce enterprise profile separation.
  GetProfile()->GetPrefs()->SetString(prefs::kManagedAccountsSigninRestriction,
                                      "primary_account_strict");
  AccountInfo account_info =
      MakeAccountInfoAvailableAndUpdate("alice@example.com", "example.com");
  // Create another profile with a browser window.
  ProfileManager* profile_manager = g_browser_process->profile_manager();
  const base::FilePath profile_path =
      profile_manager->GenerateNextProfileDirectoryPath();
  ui_test_utils::BrowserCreatedObserver browser_created_observer;
  base::RunLoop loop;
  Profile* other_profile = nullptr;
  profiles::SwitchToProfile(
      profile_path, /*always_create=*/true,
      base::BindLambdaForTesting(
          [&other_profile, &loop](BrowserWindowInterface* browser) {
            other_profile = browser->GetProfile();
            loop.Quit();
          }));
  loop.Run();
  ASSERT_TRUE(other_profile);
  const BrowserWindowInterface* const other_browser =
      browser_created_observer.Wait();
  ASSERT_TRUE(other_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  ASSERT_EQ(other_browser->GetProfile(), other_profile);
  // Add the account to the other profile.
  signin::IdentityManager* other_identity_manager =
      IdentityManagerFactory::GetForProfile(other_profile);

  signin::MakePrimaryAccountAvailable(other_identity_manager,
                                      account_info.GetEmail(),
                                      signin::ConsentLevel::kSignin);
  enterprise_util::SetUserAcceptedAccountManagement(other_profile, true);

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();
  int other_original_tab_count = other_browser->GetTabStripModel()->count();

  // Start the interception.
  GetInterceptorDelegate(GetProfile())
      ->set_expected_interception_type(
          WebSigninInterceptor::SigninInterceptionType::kProfileSwitchForced);
  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  // Add the account to the cookies (simulates the account reconcilor).
  signin::SetCookieAccounts(
      other_identity_manager, test_url_loader_factory(),
      {{std::string(account_info.GetEmail()), account_info.GetGaiaId()}});

  // Wait until the tab is moved to the other browser.
  EXPECT_TRUE(base::test::RunUntil([&]() {
    return browser()->GetTabStripModel()->count() == original_tab_count - 1;
  }));

  // The tab was moved to the new browser.
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(other_browser->GetTabStripModel()->count(),
            other_original_tab_count + 1);
  EXPECT_EQ(other_browser->GetTabStripModel()
                ->GetActiveWebContents()
                ->GetVisibleURL(),
            intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::
                      kInterceptEnterpriseForcedProfileSwitch);
  // First run experience was not shown.
  EXPECT_EQ(GetInterceptorDelegate(other_profile)->fre_browser(), nullptr);
  EXPECT_EQ(GetInterceptorDelegate(GetProfile())->fre_browser(), nullptr);
}

// Tests the complete interception flow including profile and browser creation.
IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorBrowserTest, InterceptionTest) {
  base::HistogramTester histogram_tester;
  // Setup profile for interception.
  identity_test_env()->MakePrimaryAccountAvailable(
      "alice@example.com", signin::ConsentLevel::kSignin);
  AccountInfo account_info = MakeAccountInfoAvailableAndUpdate(
      "bob@example.com", /*hosted_domain=*/std::string());

  SetupGaiaResponses();

  int64_t search_engine_choice_timestamp =
      base::Time::Now().ToDeltaSinceWindowsEpoch().InSeconds();
  const char kChoiceVersion[] = "1.2.3.4";
  PrefService* pref_service = browser()->GetProfile()->GetPrefs();
  pref_service->SetInt64(
      prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
      search_engine_choice_timestamp);
  pref_service->SetString(
      prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
      kChoiceVersion);

  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(browser()->GetProfile());
  SetUserSelectedDefaultSearchProvider(template_url_service);

  // Add a tab.
  GURL intercepted_url = embedded_test_server()->GetURL("/defaultresponse");
  content::WebContents* web_contents = AddTab(intercepted_url);
  int original_tab_count = browser()->GetTabStripModel()->count();

  // Do the signin interception.
  EXPECT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 1u);
  Profile* new_profile = InterceptAndWaitProfileCreation(
      web_contents, account_info.GetAccountId());
  ASSERT_TRUE(new_profile);
  FakeDiceWebSigninInterceptorDelegate* source_interceptor_delegate =
      GetInterceptorDelegate(GetProfile());
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_shown());
  signin::IdentityManager* new_identity_manager =
      IdentityManagerFactory::GetForProfile(new_profile);
  EXPECT_TRUE(new_identity_manager->HasAccountWithRefreshToken(
      account_info.GetAccountId()));

  IdentityTestEnvironmentProfileAdaptor adaptor(new_profile);
  adaptor.identity_test_env()->SetAutomaticIssueOfAccessTokens(true);

  // Check the profile name.
  ProfileAttributesStorage& storage =
      g_browser_process->profile_manager()->GetProfileAttributesStorage();
  ProfileAttributesEntry* entry =
      storage.GetProfileAttributesWithPath(new_profile->GetPath());
  ASSERT_TRUE(entry);
  EXPECT_EQ("givenname", base::UTF16ToUTF8(entry->GetLocalProfileName()));
  // Check the profile color.
  EXPECT_TRUE(ThemeServiceFactory::GetForProfile(new_profile)
                  ->GetUserColor()
                  .has_value());

  PrefService* new_pref_service = new_profile->GetPrefs();
  EXPECT_EQ(new_pref_service->GetInt64(
                prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
            search_engine_choice_timestamp);
  EXPECT_EQ(new_pref_service->GetString(
                prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
            kChoiceVersion);

  TemplateURLService* new_template_url_service =
      TemplateURLServiceFactory::GetForProfile(new_profile);
  EXPECT_EQ(new_template_url_service->GetDefaultSearchProvider()->short_name(),
            base::UTF8ToUTF16(std::string(kCustomSearchEngineDomain)));

  // A browser has been created for the new profile and the tab was moved there.
  Browser* added_browser = ui_test_utils::WaitForBrowserToOpen();
  ASSERT_TRUE(added_browser);
  ASSERT_EQ(GlobalBrowserCollection::GetInstance()->GetSize(), 2u);
  EXPECT_EQ(added_browser->GetProfile(), new_profile);
  EXPECT_EQ(browser()->GetTabStripModel()->count(), original_tab_count - 1);
  EXPECT_EQ(
      added_browser->tab_strip_model()->GetActiveWebContents()->GetVisibleURL(),
      intercepted_url);

  CheckHistograms(histogram_tester,
                  SigninInterceptionHeuristicOutcome::kInterceptMultiUser);
  // Interception bubble is destroyed in the source profile, and was not shown
  // in the new profile.
  FakeDiceWebSigninInterceptorDelegate* new_interceptor_delegate =
      GetInterceptorDelegate(new_profile);
  EXPECT_TRUE(source_interceptor_delegate->intercept_bubble_destroyed());
  EXPECT_FALSE(new_interceptor_delegate->intercept_bubble_shown());
  EXPECT_FALSE(new_interceptor_delegate->intercept_bubble_destroyed());
  // First run experience UI was shown exactly once in the new profile.
  EXPECT_EQ(new_interceptor_delegate->fre_browser(), added_browser);
  EXPECT_EQ(new_interceptor_delegate->fre_account_id(),
            account_info.GetAccountId());
  EXPECT_EQ(source_interceptor_delegate->fre_browser(), nullptr);
}

// =============================================================================
// Regression test for b/504183747
// =============================================================================
//
// This test verifies that any ongoing policy fetch is cancelled when the
// interception info fetch timeout fires, preventing use-after-free issues from
// late policy responses arriving during profile creation.
//
// Scenario (mirrors production):
//  1) A managed account is web-signed-in. ProcessInterceptionOrWait() is called
//     with an AccountInfo whose `is_subject_to_parental_controls` capability is
//     still kUnknown (capabilities fetch hasn't completed). This starts the
//     UserCloudSigninRestrictionPolicyFetcher with that stale AccountInfo
//     bound by value into the completion callback, and starts observing the
//     IdentityManager.
//  2) The 5s interception-info timeout fires.
//     OnInterceptionInfoFetchTimeout() explicitly cancels any pending policy
//     fetch, completely eliminating the possibility of a late response.
//  3) The user clicks Accept. OnProfileCreationChoice() is invoked and starts
//     the DiceSignedInProfileCreator, which kicks off async profile creation.
//
// Before the fix:
//     The IdentityManager observation was reset, but the pending policy fetcher
//     was left active.
//     If the user clicked Accept and profile creation began, a late policy
//     response could arrive, re-register the IdentityManager observer, and
//     cause a use-after-free when MoveAccount() later triggered account removal
//     notifications.
//
// After the fix:
//     The fetcher is explicitly cancelled on timeout, preventing any late
//     policy response from re-registering the observer.
//
// In this browser test, we use IdentityTestEnvironment for accounts and call
// the private timer callback directly (via friend access) to simulate the
// timeout.

namespace {

// Delegate that captures the interception-bubble callback so the test can
// invoke it at a precise point (simulating the user clicking "Accept" after
// the timeout but before the late policy response).
class CapturingInterceptorDelegate : public DiceWebSigninInterceptorDelegate {
 public:
  std::unique_ptr<ScopedWebSigninInterceptionBubbleHandle>
  ShowSigninInterceptionBubble(
      content::WebContents* web_contents,
      const BubbleParameters& bubble_parameters,
      base::OnceCallback<void(SigninInterceptionResult)> callback) override {
    callback_ = std::move(callback);
    bubble_shown_ = true;
    return std::make_unique<FakeBubbleHandle>();
  }

  void ShowFirstRunExperienceInNewProfile(
      BrowserWindowInterface* browser,
      const CoreAccountId& account_id,
      WebSigninInterceptor::SigninInterceptionType type) override {}

  void ShowSigninError(content::WebContents* web_contents,
                       const SigninUIError& error) override {}

  bool bubble_shown() const { return bubble_shown_; }
  base::OnceCallback<void(SigninInterceptionResult)> TakeCallback() {
    return std::move(callback_);
  }

  base::WeakPtr<CapturingInterceptorDelegate> GetWeakPtr() {
    return weak_factory_.GetWeakPtr();
  }

 private:
  bool bubble_shown_ = false;
  base::OnceCallback<void(SigninInterceptionResult)> callback_;
  base::WeakPtrFactory<CapturingInterceptorDelegate> weak_factory_{this};
};

}  // namespace

class DiceWebSigninInterceptorLatePolicyCallbackUAFTest
    : public SigninBrowserTestBase {
 public:
  DiceWebSigninInterceptorLatePolicyCallbackUAFTest()
      : SigninBrowserTestBase(/*use_main_profile=*/true) {}

  content::WebContents* AddTab(const GURL& url) {
    ui_test_utils::NavigateToURLWithDisposition(
        browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
        ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }

  CapturingInterceptorDelegate* delegate() { return delegate_.get(); }

  // Directly invoke the private 5s-timeout handler (this is exactly what the
  // PostDelayedTask at dice_web_signin_interceptor.cc:627 would call).
  void FireInfoFetchTimeout(DiceWebSigninInterceptor* interceptor) {
    interceptor->OnInterceptionInfoFetchTimeout();
  }

  bool IsObservingIdentityManager(DiceWebSigninInterceptor* interceptor) {
    return interceptor->account_info_update_observation_.IsObserving();
  }

  bool HasPendingFetcher(DiceWebSigninInterceptor* interceptor) {
    return interceptor->state_
               ->account_level_signin_restriction_policy_fetcher_ != nullptr;
  }

  bool HasProfileCreator(DiceWebSigninInterceptor* interceptor) {
    return interceptor->state_->dice_signed_in_profile_creator_ != nullptr;
  }

 protected:
  void SetUpOnMainThread() override {
    SigninBrowserTestBase::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());
  }

 private:
  void OnWillCreateBrowserContextServices(
      content::BrowserContext* context) override {
    SigninBrowserTestBase::OnWillCreateBrowserContextServices(context);
    DiceWebSigninInterceptorFactory::GetInstance()->SetTestingFactory(
        context,
        base::BindRepeating(&DiceWebSigninInterceptorLatePolicyCallbackUAFTest::
                                BuildInterceptor,
                            base::Unretained(this)));
  }

  std::unique_ptr<KeyedService> BuildInterceptor(
      content::BrowserContext* context) {
    auto delegate = std::make_unique<CapturingInterceptorDelegate>();
    if (!delegate_) {
      delegate_ = delegate->GetWeakPtr();
    }
    return std::make_unique<DiceWebSigninInterceptor>(
        Profile::FromBrowserContext(context), std::move(delegate),
        &profile_metrics_service_);
  }

  base::WeakPtr<CapturingInterceptorDelegate> delegate_;
  metrics::ProfileMetricsService profile_metrics_service_{
      metrics::ProfileMetricsContext(1)};
  web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;
};

IN_PROC_BROWSER_TEST_F(DiceWebSigninInterceptorLatePolicyCallbackUAFTest,
                       UseAfterFreeOnLatePolicyResponse) {
  // (1) Source profile already has a primary account (so the bubble type is
  //     SigninInterceptionType::kEnterprise -> OnProfileCreationChoice).
  identity_test_env()->MakePrimaryAccountAvailable(
      "primary@gmail.com", signin::ConsentLevel::kSignin);

  // Intercepted managed account: IsRequiredExtendedAccountInfoAvailable() is
  // true but is_subject_to_parental_controls() is left kUnknown so
  // IsFullExtendedAccountInfoAvailable() is false. This is exactly the state
  // of a freshly-DICE-added managed account before the capabilities fetch
  // completes.
  AccountInfo account_info =
      identity_test_env()->MakeAccountAvailable("alice@example.com");
  account_info = AccountInfo::Builder(account_info)
                     .SetFullName("fullname")
                     .SetGivenName("givenname")
                     .SetHostedDomain("example.com")
                     .SetAvatarUrl("https://example.com")
                     .SetLocale("en")
                     .Build();
  AccountCapabilitiesTestMutator mutator(&account_info);
  mutator.set_is_subject_to_enterprise_features(true);
  mutator.set_is_subject_to_account_level_enterprise_policies(true);
  ASSERT_TRUE(account_info.IsValid());
  ASSERT_EQ(
      signin::Tribool::kUnknown,
      account_info.GetAccountCapabilities().is_subject_to_parental_controls());
  identity_test_env()->UpdateAccountInfoForAccount(account_info);

  content::WebContents* web_contents =
      AddTab(embedded_test_server()->GetURL("/defaultresponse"));

  DiceWebSigninInterceptor* interceptor =
      DiceWebSigninInterceptorFactory::GetForProfile(GetProfile());
  // Do NOT pre-set profile-separation policies for testing: we want the real
  // UserCloudSigninRestrictionPolicyFetcher to be created so its callback (with
  // the stale AccountInfo bound by value) is left pending.
  interceptor->SetInterceptedAccountProfileSeparationPoliciesForTesting(
      std::nullopt);

  // This is what ProcessDiceHeaderDelegateImpl calls after the Gaia DICE token
  // exchange completes.
  interceptor->MaybeInterceptWebSignin(
      web_contents, account_info.GetAccountId(),
      signin_metrics::AccessPoint::kWebSignin,
      /*is_new_account=*/true,
      /*is_sync_signin=*/false,
      /*primary_is_connected=*/signin::Tribool::kUnknown);
  interceptor->OnDiceSigninSessionComplete(account_info.GetAccountId(), {});

  // The fetcher was created (its access-token request is pending in the test
  // IdentityManager and is never answered, simulating a slow/stalled policy
  // endpoint), and the IdentityManager is being observed.
  ASSERT_TRUE(HasPendingFetcher(interceptor));
  ASSERT_TRUE(IsObservingIdentityManager(interceptor));
  ASSERT_FALSE(delegate()->bubble_shown());

  // (2) The 5s timeout fires. account_info_update_observation_ is reset and the
  //     pending policy fetcher is cancelled. The bubble is shown.
  FireInfoFetchTimeout(interceptor);
  ASSERT_TRUE(delegate()->bubble_shown());
  ASSERT_FALSE(HasPendingFetcher(interceptor));
  ASSERT_FALSE(IsObservingIdentityManager(interceptor));

  // (3) The user clicks Accept. OnProfileCreationChoice() passes the
  //     CHECK(!IsObserving()) and constructs DiceSignedInProfileCreator, which
  //     asynchronously starts CreateMultiProfileAsync.
  ProfileWaiter profile_waiter;
  delegate()->TakeCallback().Run(SigninInterceptionResult::kAccepted);
  ASSERT_TRUE(HasProfileCreator(interceptor));
  ASSERT_FALSE(IsObservingIdentityManager(interceptor));

  // Let the async profile creation complete. The observer is not active,
  // so calling MoveAccount() will not trigger a premature Reset() and
  // destruction of DiceSignedInProfileCreator.
  profile_waiter.WaitForProfileAdded();
}
