chromium/chromeos/ash/components/language_packs/language_pack_manager_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 "chromeos/ash/components/language_packs/language_pack_manager.h"

#include <string>
#include <string_view>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice.pb.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/dlcservice/fake_dlcservice_client.h"
#include "components/session_manager/core/session_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/dlcservice/dbus-constants.h"

using ::dlcservice::DlcState;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Each;
using ::testing::Field;
using ::testing::FieldsAre;
using ::testing::Invoke;
using ::testing::Property;
using ::testing::ResultOf;
using ::testing::Return;
using ::testing::StartsWith;
using ::testing::WithArg;

namespace ash::language_packs {

namespace {

constexpr char kFakeDlcId[] = "FakeDlc";
constexpr char kSupportedLocale[] = "es";
constexpr char kHistogramGetPackStateFeatureId[] =
    "ChromeOS.LanguagePacks.GetPackState.FeatureId";
constexpr char kHistogramInstallPackSuccess[] =
    "ChromeOS.LanguagePacks.InstallPack.Success";
constexpr char kHistogramInstallBasePackFeatureId[] =
    "ChromeOS.LanguagePacks.InstallBasePack.FeatureId";
constexpr char kHistogramOobeValidLocale[] =
    "ChromeOS.LanguagePacks.Oobe.ValidLocale";
constexpr char kHistogramUninstallCompleteSuccess[] =
    "ChromeOS.LanguagePacks.UninstallComplete.Success";

// We need a mock callback so that we can check that it gets called.
class CallbackForTesting {
 public:
  OnInstallCompleteCallback GetInstallCallback() {
    return base::BindOnce(&CallbackForTesting::Callback,
                          base::Unretained(this));
  }

  GetPackStateCallback GetPackStateCallback() {
    return base::BindOnce(&CallbackForTesting::Callback,
                          base::Unretained(this));
  }

  OnUninstallCompleteCallback GetRemoveCallback() {
    return base::BindOnce(&CallbackForTesting::Callback,
                          base::Unretained(this));
  }

  OnUpdatePacksForOobeCallback GetOobeCallback() {
    return base::BindOnce(&CallbackForTesting::Callback,
                          base::Unretained(this));
  }

  MOCK_METHOD(void, Callback, (const PackResult&), ());
};

class MockObserver : public LanguagePackManager::Observer {
 public:
  MOCK_METHOD(void, OnPackStateChanged, (const PackResult& pack_result));
};

// Utility function that creates a DlcState with no error, populated with id
// corresponding to German handwriting recognition and path.
DlcState CreateInstalledState() {
  DlcState output;
  output.set_state(dlcservice::DlcState_State_INSTALLED);
  output.set_id("handwriting-de");
  output.set_root_path("/path");
  return output;
}

DlcState CreateTtsInstalledState(const std::string& locale) {
  DlcState output;
  output.set_state(dlcservice::DlcState_State_INSTALLED);
  output.set_id(base::StrCat({"tts-", locale, "-c"}));
  output.set_root_path("/path");
  return output;
}

}  // namespace

class LanguagePackManagerTest : public testing::Test {
 public:
  void SetUp() override {
    session_manager_ = std::make_unique<session_manager::SessionManager>();

    ResetPackResult();

    base::RunLoop().RunUntilIdle();
  }

  void InstallTestCallback(const PackResult& pack_result) {
    pack_result_ = pack_result;
  }

  void GetPackStateTestCallback(const PackResult& pack_result) {
    pack_result_ = pack_result;
  }

  void RemoveTestCallback(const PackResult& pack_result) {
    pack_result_ = pack_result;
  }

  void OobeTestCallback(const PackResult& pack_result) {
    pack_result_ = pack_result;
  }

 protected:
  PackResult pack_result_;
  FakeDlcserviceClient dlcservice_client_;
  std::unique_ptr<session_manager::SessionManager> session_manager_;

 private:
  base::test::SingleThreadTaskEnvironment task_environment_;

  void ResetPackResult() {
    PackResult temp = PackResult();
    pack_result_ = temp;
  }
};

TEST_F(LanguagePackManagerTest, InstallSuccessTest) {
  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingSuccess, 0);
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingFailure, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::InstallPack(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::InstallTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kHandwritingFeatureId);
  EXPECT_EQ(pack_result_.language_code, kSupportedLocale);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingSuccess, 1);
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingFailure, 0);
}

TEST_F(LanguagePackManagerTest, InstallFailureTest) {
  dlcservice_client_.set_install_error(dlcservice::kErrorInternal);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingSuccess, 0);
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingFailure, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::InstallPack(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::InstallTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kOther);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingSuccess, 0);
  histogram_tester.ExpectBucketCount(
      kHistogramInstallPackSuccess, FeatureSuccessEnum::kHandwritingFailure, 1);
}

TEST_F(LanguagePackManagerTest, InstallWrongIdTest) {
  // Note: no UMA metrics are reconded in this case, because there is no call to
  // DLC Service, hence no success nor failure.

  LanguagePackManager::InstallPack(
      kFakeDlcId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::InstallTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kWrongId);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);
}

// Check that the callback is actually called.
TEST_F(LanguagePackManagerTest, InstallCallbackTest) {
  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  testing::StrictMock<CallbackForTesting> callback;
  EXPECT_CALL(callback, Callback(_));

  LanguagePackManager::InstallPack(kFakeDlcId, kSupportedLocale,
                                   callback.GetInstallCallback());
  base::RunLoop().RunUntilIdle();
}

TEST_F(LanguagePackManagerTest, GetPackStateSuccessTest) {
  dlcservice_client_.set_get_dlc_state_error(
      GetDlcIdForLanguagePack(kHandwritingFeatureId, kSupportedLocale).value(),
      dlcservice::kErrorNone);

  dlcservice::DlcState dlc_state;
  dlc_state.set_state(dlcservice::DlcState_State_INSTALLED);
  dlc_state.set_is_verified(true);
  dlc_state.set_root_path("/path");
  dlcservice_client_.set_dlc_state(
      GetDlcIdForLanguagePack(kHandwritingFeatureId, kSupportedLocale).value(),
      dlc_state);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramGetPackStateFeatureId,
                                     FeatureIdsEnum::kHandwriting, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::GetPackState(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::GetPackStateTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kHandwritingFeatureId);
  EXPECT_EQ(pack_result_.language_code, kSupportedLocale);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramGetPackStateFeatureId,
                                     FeatureIdsEnum::kHandwriting, 1);
}

TEST_F(LanguagePackManagerTest, GetPackStateSuccessNotInstalledButVerified) {
  std::string dlc_id =
      GetDlcIdForLanguagePack(kHandwritingFeatureId, kSupportedLocale).value();
  dlcservice_client_.set_get_dlc_state_error(dlc_id, dlcservice::kErrorNone);
  dlcservice::DlcState dlc_state;
  dlc_state.set_id(dlc_id);
  dlc_state.set_state(dlcservice::DlcState_State_NOT_INSTALLED);
  dlc_state.set_is_verified(true);
  dlcservice_client_.set_install_root_path("/path");
  dlcservice_client_.set_dlc_state(dlc_id, dlc_state);

  LanguagePackManager::GetPackState(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::GetPackStateTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kHandwritingFeatureId);
  EXPECT_EQ(pack_result_.language_code, kSupportedLocale);
  base::test::TestFuture<std::string_view, const dlcservice::DlcsWithContent&>
      future;
  dlcservice_client_.GetExistingDlcs(future.GetCallback());
  const dlcservice::DlcsWithContent& dlcs = future.Get<1>();
  EXPECT_THAT(
      dlcs.dlc_infos(),
      ElementsAre(Property(
          "id", &dlcservice::DlcsWithContent::DlcInfo::id,
          *GetDlcIdForLanguagePack(kHandwritingFeatureId, kSupportedLocale))));
}

TEST_F(LanguagePackManagerTest, GetPackStateFailureTest) {
  dlcservice_client_.set_get_dlc_state_error(
      GetDlcIdForLanguagePack(kHandwritingFeatureId, kSupportedLocale).value(),
      dlcservice::kErrorInternal);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramGetPackStateFeatureId,
                                     FeatureIdsEnum::kHandwriting, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::GetPackState(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::GetPackStateTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kOther);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramGetPackStateFeatureId,
                                     FeatureIdsEnum::kHandwriting, 1);
}

TEST_F(LanguagePackManagerTest, GetPackStateWrongIdTest) {
  // Note: no UMA metrics are reconded in this case, because there is no call to
  // DLC Service, hence no success nor failure.

  LanguagePackManager::GetPackState(
      kFakeDlcId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::GetPackStateTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kWrongId);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);
}

// Check that the callback is actually called.
TEST_F(LanguagePackManagerTest, GetPackStateCallbackTest) {
  dlcservice_client_.set_get_dlc_state_error(kFakeDlcId,
                                             dlcservice::kErrorNone);

  testing::StrictMock<CallbackForTesting> callback;
  EXPECT_CALL(callback, Callback(_));

  LanguagePackManager::GetPackState(kFakeDlcId, kSupportedLocale,
                                    callback.GetPackStateCallback());
  base::RunLoop().RunUntilIdle();
}

TEST_F(LanguagePackManagerTest, RemovePackSuccessTest) {
  dlcservice_client_.set_uninstall_error(dlcservice::kErrorNone);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     1 /* True */, 0);
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     0 /* False */, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::RemovePack(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::RemoveTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kNotInstalled);
  EXPECT_EQ(pack_result_.feature_id, kHandwritingFeatureId);
  EXPECT_EQ(pack_result_.language_code, kSupportedLocale);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     1 /* True */, 1);
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     0 /* False */, 0);
}

TEST_F(LanguagePackManagerTest, RemovePackFailureTest) {
  dlcservice_client_.set_uninstall_error(dlcservice::kErrorInternal);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     1 /* True */, 0);
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     0 /* False */, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::RemovePack(
      kHandwritingFeatureId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::RemoveTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kOther);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     1 /* True */, 0);
  histogram_tester.ExpectBucketCount(kHistogramUninstallCompleteSuccess,
                                     0 /* False */, 1);
}

TEST_F(LanguagePackManagerTest, RemovePackWrongIdTest) {
  // Note: no UMA metrics are reconded in this case, because there is no call to
  // DLC Service, hence no success nor failure.

  LanguagePackManager::RemovePack(
      kFakeDlcId, kSupportedLocale,
      base::BindOnce(&LanguagePackManagerTest::RemoveTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kWrongId);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);
}

// Check that the callback is actually called.
TEST_F(LanguagePackManagerTest, RemovePackCallbackTest) {
  dlcservice_client_.set_uninstall_error(dlcservice::kErrorNone);

  testing::StrictMock<CallbackForTesting> callback;
  EXPECT_CALL(callback, Callback(_));

  LanguagePackManager::RemovePack(kFakeDlcId, kSupportedLocale,
                                  callback.GetRemoveCallback());
  base::RunLoop().RunUntilIdle();
}

TEST_F(LanguagePackManagerTest, InstallObserverTest) {
  LanguagePackManager manager;

  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");
  const DlcState dlc_state = CreateInstalledState();
  MockObserver observer;

  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(0);
  dlcservice_client_.NotifyObserversForTest(dlc_state);

  // Add an Observer and expect it to be notified.
  manager.AddObserver(&observer);
  EXPECT_CALL(observer, OnPackStateChanged(_))
      .With(
          FieldsAre(AllOf(Field(&PackResult::feature_id, kHandwritingFeatureId),
                          Field(&PackResult::language_code, "de"))))
      .Times(1);
  dlcservice_client_.NotifyObserversForTest(dlc_state);
}

TEST_F(LanguagePackManagerTest, RemoveObserverTest) {
  LanguagePackManager manager;

  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");
  const DlcState dlc_state = CreateInstalledState();
  MockObserver observer;

  // Add an Observer and expect it to be notified.
  manager.AddObserver(&observer);
  EXPECT_CALL(observer, OnPackStateChanged(_))
      .With(
          FieldsAre(AllOf(Field(&PackResult::feature_id, kHandwritingFeatureId),
                          Field(&PackResult::language_code, "de"))))
      .Times(1);
  dlcservice_client_.NotifyObserversForTest(dlc_state);

  // Remove the Observer and there should be no more notifications.
  manager.RemoveObserver(&observer);
  EXPECT_CALL(observer, OnPackStateChanged(_)).Times(0);
  dlcservice_client_.NotifyObserversForTest(dlc_state);

  base::RunLoop().RunUntilIdle();
}

// Check that all supported locales are available.
TEST_F(LanguagePackManagerTest, CheckAllLocalesAvailable) {
  // Handwriting Recognition.
  const std::vector<std::string> handwriting({
      "am", "ar", "be", "bg", "bn",  "ca", "cs", "da", "de", "el", "en",
      "es", "et", "fa", "fi", "fil", "fr", "ga", "gu", "hi", "hr", "hu",
      "hy", "id", "is", "it", "iw",  "ja", "ka", "kk", "km", "kn", "ko",
      "lo", "lt", "lv", "ml", "mn",  "mr", "ms", "mt", "my", "ne", "nl",
      "no", "or", "pa", "pl", "pt",  "ro", "ru", "si", "sk", "sl", "sr",
      "sv", "ta", "te", "th", "ti",  "tr", "uk", "ur", "vi", "zh", "zh-HK",
  });
  for (const auto& locale : handwriting) {
    EXPECT_TRUE(
        LanguagePackManager::IsPackAvailable(kHandwritingFeatureId, locale));
  }

  // TTS.
  const std::vector<std::string> tts({
      "bn-bd", "cs-cz", "da-dk", "de-de", "el-gr",  "en-au", "en-gb",
      "en-us", "es-es", "es-us", "fi-fi", "fil-ph", "fr-fr", "hi-in",
      "hu-hu", "id-id", "it-it", "ja-jp", "km-kh",  "ko-kr", "nb-no",
      "ne-np", "nl-nl", "pl-pl", "pt-br", "si-lk",  "sk-sk", "sv-se",
      "th-th", "tr-tr", "uk-ua", "vi-vn", "yue-hk",
  });
  for (const auto& locale : tts) {
    EXPECT_TRUE(LanguagePackManager::IsPackAvailable(kTtsFeatureId, locale));
  }

  const std::vector<std::string> fonts = {"ja", "ko"};
  EXPECT_THAT(fonts, Each(ResultOf(
                         "Font pack availability",
                         [](const std::string& locale) {
                           return LanguagePackManager::IsPackAvailable(
                               kFontsFeatureId, locale);
                         },
                         true)));
}

TEST_F(LanguagePackManagerTest, IsPackAvailableFalseTest) {
  // Correct ID, wrong language (Welsh).
  bool available =
      LanguagePackManager::IsPackAvailable(kHandwritingFeatureId, "cy");
  EXPECT_FALSE(available);

  // ID doesn't exists.
  available = LanguagePackManager::IsPackAvailable("foo", "fr");
  EXPECT_FALSE(available);
}

TEST_F(LanguagePackManagerTest, InstallBasePackSuccess) {
  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramInstallBasePackFeatureId,
                                     FeatureIdsEnum::kHandwriting, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::InstallBasePack(
      kHandwritingFeatureId,
      base::BindOnce(&LanguagePackManagerTest::InstallTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kHandwritingFeatureId);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramInstallBasePackFeatureId,
                                     FeatureIdsEnum::kHandwriting, 1);
}

TEST_F(LanguagePackManagerTest, InstallBasePackFailureTestFailure) {
  dlcservice_client_.set_install_error(dlcservice::kErrorInternal);

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramInstallBasePackFeatureId,
                                     FeatureIdsEnum::kHandwriting, 0);

  // We need to use an existing Pack ID, so that we do get a result back.
  LanguagePackManager::InstallBasePack(
      kHandwritingFeatureId,
      base::BindOnce(&LanguagePackManagerTest::InstallTestCallback,
                     base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kOther);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramInstallBasePackFeatureId,
                                     FeatureIdsEnum::kHandwriting, 1);
}

// If we are not in OOBE nothing should happen.
TEST_F(LanguagePackManagerTest, UpdatePacksForOobeNotOobeTest) {
  // Set session as user logged in.
  session_manager_->SetSessionState(session_manager::SessionState::ACTIVE);
  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  testing::StrictMock<CallbackForTesting> callback;
  EXPECT_CALL(callback, Callback(_)).Times(0);

  LanguagePackManager::UpdatePacksForOobe(kSupportedLocale,
                                          callback.GetInstallCallback());
  base::RunLoop().RunUntilIdle();
}

TEST_F(LanguagePackManagerTest, UpdatePacksForOobeSuccessTest) {
  session_manager_->SetSessionState(session_manager::SessionState::OOBE);

  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     0);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     0);

  LanguagePackManager::UpdatePacksForOobe(
      "en-au", base::BindOnce(&LanguagePackManagerTest::OobeTestCallback,
                              base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kTtsFeatureId);
  EXPECT_EQ(pack_result_.language_code, "en-au");

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     1);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     0);
}

TEST_F(LanguagePackManagerTest, UpdatePacksForOobeSuccess2Test) {
  session_manager_->SetSessionState(session_manager::SessionState::OOBE);

  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     0);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     0);

  LanguagePackManager::UpdatePacksForOobe(
      "it-it", base::BindOnce(&LanguagePackManagerTest::OobeTestCallback,
                              base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kNone);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kInstalled);
  EXPECT_EQ(pack_result_.path, "/path");
  EXPECT_EQ(pack_result_.feature_id, kTtsFeatureId);
  EXPECT_EQ(pack_result_.language_code, "it");

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     1);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     0);
}

TEST_F(LanguagePackManagerTest, UpdatePacksForOobeWrongLocaleTest) {
  session_manager_->SetSessionState(session_manager::SessionState::OOBE);

  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  // Test UMA metrics: pre-condition.
  base::HistogramTester histogram_tester;
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     0);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     0);

  LanguagePackManager::UpdatePacksForOobe(
      "xxx", base::BindOnce(&LanguagePackManagerTest::OobeTestCallback,
                            base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kWrongId);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);

  // Test UMA metrics: post-condition.
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 1 /* True */,
                                     0);
  histogram_tester.ExpectBucketCount(kHistogramOobeValidLocale, 0 /* False */,
                                     1);
}

TEST_F(LanguagePackManagerTest, UpdatePacksForOobeFailureTest) {
  session_manager_->SetSessionState(session_manager::SessionState::OOBE);

  dlcservice_client_.set_install_error(dlcservice::kErrorInternal);

  LanguagePackManager::UpdatePacksForOobe(
      "es-es", base::BindOnce(&LanguagePackManagerTest::OobeTestCallback,
                              base::Unretained(this)));
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(pack_result_.operation_error, PackResult::ErrorCode::kOther);
  EXPECT_EQ(pack_result_.pack_state, PackResult::StatusCode::kUnknown);
}

struct TestCase {
  std::string dlc_locale;
  std::string language_pack_locale;
};

class LanguagePackManagerTtsTest
    : public LanguagePackManagerTest,
      public testing::WithParamInterface<TestCase> {};

INSTANTIATE_TEST_SUITE_P(,
                         LanguagePackManagerTtsTest,
                         ::testing::Values(TestCase("en-us", "en-us"),
                                           TestCase("yue-hk", "yue"),
                                           TestCase("bn-bd", "bn")));

TEST_P(LanguagePackManagerTtsTest, InstallTtsObserverTest) {
  LanguagePackManager manager;
  MockObserver observer;
  manager.AddObserver(&observer);
  dlcservice_client_.set_install_error(dlcservice::kErrorNone);
  dlcservice_client_.set_install_root_path("/path");

  std::string dlc_locale = GetParam().dlc_locale;
  std::string language_pack_locale = GetParam().language_pack_locale;
  const DlcState dlc_state = CreateTtsInstalledState(dlc_locale);
  EXPECT_CALL(observer,
              OnPackStateChanged(AllOf(
                  Field(&PackResult::feature_id, kTtsFeatureId),
                  Field(&PackResult::language_code, language_pack_locale))))
      .Times(1);
  dlcservice_client_.NotifyObserversForTest(dlc_state);

  manager.RemoveObserver(&observer);
}

}  // namespace ash::language_packs