chromium/chrome/browser/ui/ash/projector/projector_soda_installation_controller_unittest.cc

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

#include "projector_soda_installation_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/locale_update_controller.h"
#include "ash/public/cpp/projector/projector_controller.h"
#include "ash/public/cpp/projector/projector_new_screencast_precondition.h"
#include "ash/public/cpp/test/mock_projector_client.h"
#include "ash/webui/projector_app/test/mock_app_client.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/speech/speech_recognition_recognizer_client_impl.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/prefs/pref_service.h"
#include "components/soda/constants.h"
#include "components/soda/soda_installer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

inline void SetLocale(const std::string& locale) {
  g_browser_process->SetApplicationLocale(locale);
  LocaleUpdateController::Get()->OnLocaleChanged();
}

// A mocked version instance of SodaInstaller for testing purposes.
class MockSodaInstaller : public speech::SodaInstaller {
 public:
  MockSodaInstaller() = default;
  MockSodaInstaller(const MockSodaInstaller&) = delete;
  MockSodaInstaller& operator=(const MockSodaInstaller&) = delete;
  ~MockSodaInstaller() override = default;

  MOCK_METHOD(base::FilePath, GetSodaBinaryPath, (), (const, override));
  MOCK_METHOD(base::FilePath,
              GetLanguagePath,
              (const std::string&),
              (const, override));
  MOCK_METHOD(void,
              InstallLanguage,
              (const std::string&, PrefService*),
              (override));
  MOCK_METHOD(void,
              UninstallLanguage,
              (const std::string&, PrefService*),
              (override));
  MOCK_METHOD(std::vector<std::string>,
              GetAvailableLanguages,
              (),
              (const, override));
  MOCK_METHOD(void, InstallSoda, (PrefService*), (override));
  MOCK_METHOD(void, UninstallSoda, (PrefService*), (override));
};

const char kEnglishLocale[] = "en-US";
const char kNonEnglishLocale[] = "fr";

}  // namespace

class ProjectorSodaInstallationControllerTest : public ChromeAshTestBase {
 public:
  ProjectorSodaInstallationControllerTest() {
    scoped_feature_list_.InitWithFeatures(
        {features::kOnDeviceSpeechRecognition},
        {features::kInternalServerSideSpeechRecognition,
         features::kForceEnableServerSideSpeechRecognitionForDev});
  }
  ProjectorSodaInstallationControllerTest(
      const ProjectorSodaInstallationControllerTest&) = delete;
  ProjectorSodaInstallationControllerTest& operator=(
      const ProjectorSodaInstallationControllerTest&) = delete;
  ~ProjectorSodaInstallationControllerTest() override = default;

  // ChromeAshTestBase:
  void SetUp() override {
    ChromeAshTestBase::SetUp();

    ASSERT_TRUE(testing_profile_manager_.SetUp());
    testing_profile_ = ProfileManager::GetPrimaryUserProfile();

    soda_installer_ = std::make_unique<MockSodaInstaller>();
    ON_CALL(*soda_installer(), GetAvailableLanguages)
        .WillByDefault(
            testing::Return(std::vector<std::string>({kEnglishLocale})));

    mock_client_ = std::make_unique<MockProjectorClient>();

    ON_CALL(*mock_client_, GetSpeechRecognitionAvailability)
        .WillByDefault(
            testing::Invoke([&]() -> ash::SpeechRecognitionAvailability {
              SpeechRecognitionAvailability availability;
              availability.on_device_availability =
                  SpeechRecognitionRecognizerClientImpl::
                      GetOnDeviceSpeechRecognitionAvailability(
                          g_browser_process->GetApplicationLocale());
              return availability;
            }));

    projector_controller().SetClient(mock_client_.get());
    mock_app_client_ = std::make_unique<MockAppClient>();

    SetLocale(kEnglishLocale);

    soda_installation_controller_ =
        std::make_unique<ProjectorSodaInstallationController>(
            mock_app_client_.get(), &projector_controller());
  }

  void TearDown() override {
    soda_installation_controller_.reset();
    mock_app_client_.reset();
    mock_client_.reset();
    soda_installer_.reset();

    ChromeAshTestBase::TearDown();
  }

  MockAppClient& app_client() { return *mock_app_client_; }
  MockProjectorClient& projector_client() { return *mock_client_; }
  ProjectorController& projector_controller() {
    return *ProjectorController::Get();
  }

  ProjectorSodaInstallationController* soda_installation_controller() {
    return soda_installation_controller_.get();
  }

  MockSodaInstaller* soda_installer() { return soda_installer_.get(); }
  speech::LanguageCode en_us() { return speech::LanguageCode::kEnUs; }
  speech::LanguageCode fr_fr() { return speech::LanguageCode::kFrFr; }

 private:
  raw_ptr<Profile, DanglingUntriaged> testing_profile_ = nullptr;

  TestingProfileManager testing_profile_manager_{
      TestingBrowserProcess::GetGlobal()};

  std::unique_ptr<MockSodaInstaller> soda_installer_;
  std::unique_ptr<MockProjectorClient> mock_client_;
  std::unique_ptr<MockAppClient> mock_app_client_;

  std::unique_ptr<ProjectorSodaInstallationController>
      soda_installation_controller_;
  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_F(ProjectorSodaInstallationControllerTest, ShouldDownloadSoda) {
  EXPECT_TRUE(soda_installation_controller()->ShouldDownloadSoda(en_us()));

  // Other languages other than English are not currently supported.
  EXPECT_FALSE(soda_installation_controller()->ShouldDownloadSoda(fr_fr()));
}

TEST_F(ProjectorSodaInstallationControllerTest, IsSpeechRecognitionAvailable) {
  EXPECT_FALSE(soda_installation_controller()->IsSodaAvailable(en_us()));

  EXPECT_CALL(app_client(), OnSodaInstalled());
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
  EXPECT_TRUE(soda_installation_controller()->IsSodaAvailable(en_us()));
  EXPECT_FALSE(soda_installation_controller()->IsSodaAvailable(fr_fr()));
}

TEST_F(ProjectorSodaInstallationControllerTest, InstallSoda) {
  ProfileManager::GetPrimaryUserProfile()->GetPrefs()->SetBoolean(
      prefs::kProjectorCreationFlowEnabled, true);

  // Test case where SODA is already installed.
  soda_installation_controller()->InstallSoda(kEnglishLocale);

  EXPECT_CALL(app_client(), OnSodaInstalled());
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
}

TEST_F(ProjectorSodaInstallationControllerTest, OnSodaInstallProgress) {
  EXPECT_CALL(app_client(), OnSodaInstallProgress(50));
  speech::SodaInstaller::GetInstance()->NotifySodaProgressForTesting(50);
}

TEST_F(ProjectorSodaInstallationControllerTest, OnSodaInstallErrorUnspecified) {
  NewScreencastPrecondition soda_install_error(
      NewScreencastPreconditionState::kDisabled,
      {NewScreencastPreconditionReason::kSodaInstallationErrorUnspecified});
  EXPECT_CALL(app_client(), OnSodaInstallError());
  EXPECT_CALL(projector_client(),
              OnNewScreencastPreconditionChanged(soda_install_error));
  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
      en_us(), speech::SodaInstaller::ErrorCode::kUnspecifiedError);
  EXPECT_EQ(projector_controller().GetNewScreencastPrecondition(),
            soda_install_error);
}

TEST_F(ProjectorSodaInstallationControllerTest, OnSodaInstallErrorNeedsReboot) {
  NewScreencastPrecondition soda_install_error(
      NewScreencastPreconditionState::kDisabled,
      {NewScreencastPreconditionReason::kSodaInstallationErrorNeedsReboot});
  EXPECT_CALL(app_client(), OnSodaInstallError());
  EXPECT_CALL(projector_client(),
              OnNewScreencastPreconditionChanged(soda_install_error));
  speech::SodaInstaller::GetInstance()->NotifySodaErrorForTesting(
      en_us(), speech::SodaInstaller::ErrorCode::kNeedsReboot);
  EXPECT_EQ(projector_controller().GetNewScreencastPrecondition(),
            soda_install_error);
}

// Prevents a regression to b/228899579.
TEST_F(ProjectorSodaInstallationControllerTest,
       OnLocaleChangedBeforeSodaInstall) {
  NewScreencastPrecondition locale_not_supported(
      NewScreencastPreconditionState::kDisabled,
      {NewScreencastPreconditionReason::kUserLocaleNotSupported});
  EXPECT_CALL(projector_client(),
              OnNewScreencastPreconditionChanged(locale_not_supported));

  // The locale changes to the user's preferences after sign in but before SODA
  // finishes installing.
  SetLocale(kNonEnglishLocale);
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
  EXPECT_TRUE(soda_installation_controller()->IsSodaAvailable(en_us()));
  EXPECT_FALSE(soda_installation_controller()->IsSodaAvailable(fr_fr()));

  EXPECT_EQ(projector_controller().GetNewScreencastPrecondition(),
            locale_not_supported);
}

// Prevents a regression to b/227626179.
TEST_F(ProjectorSodaInstallationControllerTest,
       OnLocaleChangedAfterSodaInstall) {
  ON_CALL(projector_client(), IsDriveFsMounted)
      .WillByDefault(testing::Return(true));
  EXPECT_CALL(projector_client(),
              OnNewScreencastPreconditionChanged(NewScreencastPrecondition(
                  NewScreencastPreconditionState::kEnabled,
                  {NewScreencastPreconditionReason::kEnabledBySoda})));

  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting();
  speech::SodaInstaller::GetInstance()->NotifySodaInstalledForTesting(en_us());
  EXPECT_TRUE(soda_installation_controller()->IsSodaAvailable(en_us()));
  EXPECT_FALSE(soda_installation_controller()->IsSodaAvailable(fr_fr()));

  NewScreencastPrecondition locale_not_supported(
      NewScreencastPreconditionState::kDisabled,
      {NewScreencastPreconditionReason::kUserLocaleNotSupported});

  EXPECT_CALL(projector_client(),
              OnNewScreencastPreconditionChanged(locale_not_supported));

  // The locale changes to the user's preferences after sign in but after SODA
  // finishes installing.
  SetLocale(kNonEnglishLocale);

  EXPECT_EQ(projector_controller().GetNewScreencastPrecondition(),
            locale_not_supported);
}

}  // namespace ash