chromium/chrome/browser/ui/views/bruschetta/bruschetta_installer_view_browsertest.cc

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

#include "chrome/browser/ui/views/bruschetta/bruschetta_installer_view.h"

#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/values_test_util.h"
#include "chrome/browser/ash/bruschetta/bruschetta_installer.h"
#include "chrome/browser/ash/bruschetta/bruschetta_pref_names.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/accessibility/view_accessibility.h"

using testing::AnyNumber;
using testing::AtLeast;

namespace bruschetta {
namespace {

class BruschettaInstallerMock : public bruschetta::BruschettaInstaller {
 public:
  MOCK_METHOD(void, Cancel, ());
  MOCK_METHOD(void, Install, (std::string, std::string));
  MOCK_METHOD(void, AddObserver, (Observer*));
  MOCK_METHOD(void, RemoveObserver, (Observer*));
};

class BruschettaInstallerViewBrowserTest : public DialogBrowserTest {
 public:
  BruschettaInstallerViewBrowserTest() = default;

  // Disallow copy and assign.
  BruschettaInstallerViewBrowserTest(
      const BruschettaInstallerViewBrowserTest&) = delete;
  BruschettaInstallerViewBrowserTest& operator=(
      const BruschettaInstallerViewBrowserTest&) = delete;

  void SetBruschettaVMConfigurationPref() {
    base::Value::Dict pref;

    base::Value::Dict config;
    config.Set(prefs::kPolicyEnabledKey,
               static_cast<int>(prefs::PolicyEnabledState::INSTALL_ALLOWED));
    config.Set(prefs::kPolicyNameKey, "Config name");

    pref.Set("test-config", std::move(config));
    browser()->profile()->GetPrefs()->SetDict(prefs::kBruschettaVMConfiguration,
                                              std::move(pref));
  }

  void SetBruschettaInstallerConfigurationPref() {
    browser()->profile()->GetPrefs()->SetDict(
        prefs::kBruschettaInstallerConfiguration, base::test::ParseJsonDict(R"(
      {
        "display_name": "Display name",
        "learn_more_url": "https://example.com/learn_more"
      }
    )"));
  }

  void SetUpOnMainThread() override {
    SetBruschettaVMConfigurationPref();
    SetBruschettaInstallerConfigurationPref();
  }

  void ShowUi(const std::string& name) override {
    BruschettaInstallerView::Show(browser()->profile(), GetBruschettaAlphaId());
    view_ = BruschettaInstallerView::GetActiveViewForTesting();

    ASSERT_NE(nullptr, view_);
    ASSERT_FALSE(view_->GetWidget()->IsClosed());

    // Since expectations need to be set ahead of time, but the installer itself
    // expects to create and recreate installers as needed, we create our mocks
    // ahead of time, then each time we're called we transfer our pending mock
    // to the installer and pre-create another for the next call.
    // Note: This means all expectations need to be set before calling install,
    // you can't start installing, set expectation, then cancel/err/etc.
    installer_ = std::make_unique<BruschettaInstallerMock>();
    view_->set_installer_factory_for_testing(base::BindLambdaForTesting(
        [this](Profile* p, base::OnceClosure closure) {
          auto mock_installer = std::move(installer_);
          installer_ = std::make_unique<BruschettaInstallerMock>();
          EXPECT_CALL(*installer_, AddObserver).Times(AnyNumber());
          return static_cast<std::unique_ptr<BruschettaInstaller>>(
              std::move(mock_installer));
        }));
  }

  raw_ptr<BruschettaInstallerView, DanglingUntriaged> view_;
  std::unique_ptr<bruschetta::BruschettaInstallerMock> installer_;
};

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest, Show) {
  ShowUi("default");
  EXPECT_NE(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_NE(std::u16string::npos,
            view_->GetWindowTitle().find(u"Display name"));

  EXPECT_EQ(view_->GetLinkLabelForTesting()->GetDisplayTextForTesting(),
            l10n_util::GetStringUTF16(IDS_LEARN_MORE));
  EXPECT_EQ(
      view_->GetPrimaryMessage(),
      l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_CONFIRMATION_TITLE));
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest,
                       ShowWithNoLearnMoreUrl) {
  // We set the learn_more link for test cases by default as that's the most
  // common case, but unset it here for this specific test.
  browser()->profile()->GetPrefs()->SetDict(
      prefs::kBruschettaInstallerConfiguration, base::Value::Dict());
  ShowUi("default");
  EXPECT_NE(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_NE(std::u16string::npos, view_->GetWindowTitle().find(u"Config name"));

  // No text, because it's invisible.
  EXPECT_EQ(view_->GetLinkLabelForTesting()->GetDisplayTextForTesting(), u"");
  EXPECT_EQ(
      view_->GetPrimaryMessage(),
      l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_CONFIRMATION_TITLE));
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest,
                       CancelOnPromptScreen) {
  ShowUi("default");
  view_->CancelDialog();
  ASSERT_TRUE(view_->GetWidget()->IsClosed());
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest, InstallThenCancel) {
  ShowUi("default");
  EXPECT_CALL(*installer_, Install);
  EXPECT_CALL(*installer_, Cancel).Times(AtLeast(1));

  view_->AcceptDialog();
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));

  view_->CancelDialog();
  ASSERT_TRUE(view_->GetWidget()->IsClosed());
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest, InstallThenError) {
  ShowUi("default");
  base::RunLoop run_loop;
  EXPECT_CALL(*installer_, Install);
  EXPECT_CALL(*installer_, Cancel).Times(AtLeast(1));
  EXPECT_FALSE(view_->progress_bar_for_testing()->GetVisible());

  // Accept, then we're in the installing state.
  view_->AcceptDialog();
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_TRUE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));

  // Fail, then we're in the cleaning up state.
  view_->Error(BruschettaInstallResult::kStartVmFailed);
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_EQ(nullptr, view_->GetCancelButton());
  EXPECT_TRUE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ERROR_TITLE));
  EXPECT_EQ(
      view_->GetSecondaryMessage(),
      l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_CLEANING_UP_MESSAGE));

  // Run cleanup to completion, now we're in the error state.
  run_loop.RunUntilIdle();
  EXPECT_NE(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_FALSE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ERROR_TITLE));
  EXPECT_NE(
      view_->GetSecondaryMessage(),
      l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_CLEANING_UP_MESSAGE));

  view_->CancelDialog();
  ASSERT_TRUE(view_->GetWidget()->IsClosed());
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest, InstallThenSuccess) {
  ShowUi("default");
  EXPECT_CALL(*installer_, Install);
  EXPECT_CALL(*installer_, Cancel).Times(0);

  view_->AcceptDialog();
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));
  auto first_message = view_->GetSecondaryMessage();

  // Check that state changes update the progress message.
  view_->StateChanged(bruschetta::BruschettaInstaller::State::kStartVm);
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));
  EXPECT_NE(first_message, view_->GetSecondaryMessage());

  view_->OnInstallationEnded();

  // We close the installer upon completion since we switch to a terminal
  // window to complete the install.
  ASSERT_TRUE(view_->GetWidget()->IsClosed());
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest, InstallWithRetry) {
  ShowUi("default");
  base::RunLoop run_loop;
  EXPECT_CALL(*installer_, Install);
  EXPECT_CALL(*installer_, Cancel).Times(0);

  // Start installing
  view_->AcceptDialog();
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_TRUE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));

  // An error happened
  view_->Error(BruschettaInstallResult::kStartVmFailed);
  // Let the cleanup step complete.
  run_loop.RunUntilIdle();
  EXPECT_NE(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_FALSE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ERROR_TITLE));

  // Retry to start installing again, since the installer gets recreated we need
  // to set expectations again.
  EXPECT_CALL(*installer_, Install);

  view_->AcceptDialog();
  EXPECT_EQ(nullptr, view_->GetOkButton());
  EXPECT_NE(nullptr, view_->GetCancelButton());
  EXPECT_TRUE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->GetPrimaryMessage(),
            l10n_util::GetStringUTF16(IDS_BRUSCHETTA_INSTALLER_ONGOING_TITLE));
}

IN_PROC_BROWSER_TEST_F(BruschettaInstallerViewBrowserTest,
                       A11yProgressBarDescription) {
  ShowUi("default");

  // Start installing
  view_->AcceptDialog();
  EXPECT_TRUE(view_->progress_bar_for_testing()->GetVisible());
  EXPECT_EQ(view_->progress_bar_for_testing()
                ->GetViewAccessibility()
                .GetCachedDescription(),
            view_->GetSecondaryMessage());

  // InstallThenSuccess already checks that the secondary message changes
  // between states, so we just check that the new progress bar description
  // matches the new message.
  view_->StateChanged(bruschetta::BruschettaInstaller::State::kStartVm);
  EXPECT_EQ(view_->progress_bar_for_testing()
                ->GetViewAccessibility()
                .GetCachedDescription(),
            view_->GetSecondaryMessage());
}

}  // namespace
}  // namespace bruschetta