chromium/base/fuchsia/intl_profile_watcher_unittest.cc

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

#include "base/fuchsia/intl_profile_watcher.h"

#include <fuchsia/intl/cpp/fidl_test_base.h>
#include <lib/fidl/cpp/binding.h>
#include <memory>
#include <string>
#include <vector>

#include "base/check.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/threading/sequence_bound.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

namespace {

const char kPrimaryTimeZoneName[] = "Australia/Darwin";
const char kSecondaryTimeZoneName[] = "Africa/Djibouti";

const char kPrimaryLocaleName[] = "en-US";
const char kSecondaryLocaleName[] = "es-419";

template <typename FuchsiaStruct>
void CopyIdsToFuchsiaStruct(const std::vector<std::string>& raw_ids,
                            std::vector<FuchsiaStruct>* fuchsia_ids) {
  fuchsia_ids->clear();
  for (auto id : raw_ids) {
    FuchsiaStruct fuchsia_id;
    fuchsia_id.id = id;
    fuchsia_ids->push_back(fuchsia_id);
  }
}

fuchsia::intl::Profile CreateProfileWithTimeZones(
    const std::vector<std::string>& zone_ids) {
  fuchsia::intl::Profile profile;
  std::vector<::fuchsia::intl::TimeZoneId> time_zone_ids;
  CopyIdsToFuchsiaStruct(zone_ids, &time_zone_ids);
  profile.set_time_zones(time_zone_ids);
  return profile;
}

fuchsia::intl::Profile CreateProfileWithLocales(
    const std::vector<std::string>& locale_ids) {
  fuchsia::intl::Profile profile;
  std::vector<::fuchsia::intl::LocaleId> fuchsia_locale_ids;
  CopyIdsToFuchsiaStruct(locale_ids, &fuchsia_locale_ids);
  profile.set_locales(fuchsia_locale_ids);
  return profile;
}

// Partial fake implementation of a PropertyProvider.
class FakePropertyProvider
    : public ::fuchsia::intl::testing::PropertyProvider_TestBase {
 public:
  explicit FakePropertyProvider(
      fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider>
          provider_request)
      : binding_(this) {
    binding_.Bind(std::move(provider_request));
    DCHECK(binding_.is_bound());
  }
  FakePropertyProvider(const FakePropertyProvider&) = delete;
  FakePropertyProvider& operator=(const FakePropertyProvider&) = delete;
  ~FakePropertyProvider() override = default;

  void Close() { binding_.Close(ZX_ERR_PEER_CLOSED); }
  void SetTimeZones(const std::vector<std::string>& zone_ids) {
    CopyIdsToFuchsiaStruct(zone_ids, &time_zone_ids_);
  }
  void SetLocales(const std::vector<std::string>& locale_ids) {
    CopyIdsToFuchsiaStruct(locale_ids, &fuchsia_locale_ids_);
  }
  void NotifyChange() { binding_.events().OnChange(); }

  // PropertyProvider_TestBase implementation.
  void GetProfile(
      ::fuchsia::intl::PropertyProvider::GetProfileCallback callback) override {
    fuchsia::intl::Profile profile;
    profile.set_time_zones(time_zone_ids_);
    profile.set_locales(fuchsia_locale_ids_);
    callback(std::move(profile));
  }
  void NotImplemented_(const std::string& name) override {
    ADD_FAILURE() << "Unimplemented function called: " << name;
  }

 private:
  ::fidl::Binding<::fuchsia::intl::PropertyProvider> binding_;

  std::vector<::fuchsia::intl::TimeZoneId> time_zone_ids_;
  std::vector<::fuchsia::intl::LocaleId> fuchsia_locale_ids_;
};

class FakePropertyProviderAsync {
 public:
  explicit FakePropertyProviderAsync(
      fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider>
          provider_request)
      : thread_("Property Provider Thread") {
    CHECK(thread_.StartWithOptions(
        base::Thread::Options(base::MessagePumpType::IO, 0)));
    property_provider_ = base::SequenceBound<FakePropertyProvider>(
        thread_.task_runner(), std::move(provider_request));
  }
  FakePropertyProviderAsync(const FakePropertyProviderAsync&) = delete;
  FakePropertyProviderAsync& operator=(const FakePropertyProviderAsync&) =
      delete;
  ~FakePropertyProviderAsync() = default;

  void Close() { property_provider_.AsyncCall(&FakePropertyProvider::Close); }
  void SetTimeZones(const std::vector<std::string>& zone_ids) {
    property_provider_.AsyncCall(&FakePropertyProvider::SetTimeZones)
        .WithArgs(zone_ids);
  }
  void SetLocales(const std::vector<std::string>& locale_ids) {
    property_provider_.AsyncCall(&FakePropertyProvider::SetLocales)
        .WithArgs(locale_ids);
  }
  void NotifyChange() {
    property_provider_.AsyncCall(&FakePropertyProvider::NotifyChange);
  }

 private:
  base::Thread thread_;
  base::SequenceBound<FakePropertyProvider> property_provider_;
};

}  // namespace

class GetValuesFromIntlPropertyProviderTest : public testing::Test {
 public:
  GetValuesFromIntlPropertyProviderTest()
      : property_provider_(property_provider_ptr_.NewRequest()) {}
  GetValuesFromIntlPropertyProviderTest(
      const GetValuesFromIntlPropertyProviderTest&) = delete;
  GetValuesFromIntlPropertyProviderTest& operator=(
      const GetValuesFromIntlPropertyProviderTest&) = delete;
  ~GetValuesFromIntlPropertyProviderTest() override = default;

 protected:
  std::string GetPrimaryLocaleId() {
    fuchsia::intl::Profile profile =
        GetProfileFromPropertyProvider(std::move(property_provider_ptr_));
    return FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(profile);
  }

  std::string GetPrimaryTimeZoneId() {
    fuchsia::intl::Profile profile =
        GetProfileFromPropertyProvider(std::move(property_provider_ptr_));
    return FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(profile);
  }

  static fuchsia::intl::Profile GetProfileFromPropertyProvider(
      ::fuchsia::intl::PropertyProviderSyncPtr property_provider) {
    return FuchsiaIntlProfileWatcher::GetProfileFromPropertyProvider(
        std::move(property_provider));
  }

  ::fuchsia::intl::PropertyProviderSyncPtr property_provider_ptr_;
  FakePropertyProviderAsync property_provider_;
};

class IntlProfileWatcherTest : public testing::Test {
 public:
  IntlProfileWatcherTest()
      : property_provider_(property_provider_ptr_.NewRequest()) {}
  IntlProfileWatcherTest(const IntlProfileWatcherTest&) = delete;
  IntlProfileWatcherTest& operator=(const IntlProfileWatcherTest&) = delete;
  ~IntlProfileWatcherTest() override = default;

 protected:
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};

  std::unique_ptr<FuchsiaIntlProfileWatcher> CreateIntlProfileWatcher(
      FuchsiaIntlProfileWatcher::ProfileChangeCallback on_profile_changed) {
    return base::WrapUnique(new FuchsiaIntlProfileWatcher(
        std::move(property_provider_ptr_), std::move(on_profile_changed)));
  }

  ::fuchsia::intl::PropertyProviderPtr property_provider_ptr_;
  FakePropertyProviderAsync property_provider_;

  base::RunLoop run_loop_;
};

// Unit tests are run in an environment where intl is not provided.
// However, this is not exposed by the API.
TEST(IntlServiceNotAvailableTest, FuchsiaIntlProfileWatcher) {
  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
  base::RunLoop run_loop;

  base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback>
      on_profile_changed;
  EXPECT_CALL(on_profile_changed, Run(testing::_)).Times(0);
  auto watcher =
      std::make_unique<FuchsiaIntlProfileWatcher>(on_profile_changed.Get());
  EXPECT_TRUE(watcher);

  run_loop.RunUntilIdle();
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryTimeZoneId_RemoteNotBound) {
  // Simulate the service not actually being available.
  property_provider_.Close();
  EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryTimeZoneId_NoZones) {
  EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryTimeZoneId_SingleZone) {
  property_provider_.SetTimeZones({kPrimaryTimeZoneName});
  EXPECT_STREQ(kPrimaryTimeZoneName, GetPrimaryTimeZoneId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryTimeZoneId_SingleZoneIsEmpty) {
  property_provider_.SetTimeZones({""});
  EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryTimeZoneId_MoreThanOneZone) {
  property_provider_.SetTimeZones(
      {kPrimaryTimeZoneName, kSecondaryTimeZoneName});
  EXPECT_STREQ(kPrimaryTimeZoneName, GetPrimaryTimeZoneId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryLocaleId_RemoteNotBound) {
  // Simulate the service not actually being available.
  property_provider_.Close();
  EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryLocaleId_NoZones) {
  EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryLocaleId_SingleLocale) {
  property_provider_.SetLocales({kPrimaryLocaleName});
  EXPECT_STREQ(kPrimaryLocaleName, GetPrimaryLocaleId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryLocaleId_SingleLocaleIsEmpty) {
  property_provider_.SetLocales({""});
  EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
}

TEST_F(GetValuesFromIntlPropertyProviderTest,
       GetPrimaryLocaleId_MoreThanOneLocale) {
  property_provider_.SetLocales({kPrimaryLocaleName, kSecondaryLocaleName});
  EXPECT_STREQ(kPrimaryLocaleName, GetPrimaryLocaleId().c_str());
}

TEST_F(IntlProfileWatcherTest, NoZones_NoNotification) {
  base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
  EXPECT_CALL(callback, Run(testing::_)).Times(0);
  auto watcher = CreateIntlProfileWatcher(callback.Get());
  run_loop_.RunUntilIdle();
}

TEST_F(IntlProfileWatcherTest, ChangeNotification_AfterInitialization) {
  auto watcher = CreateIntlProfileWatcher(
      base::BindLambdaForTesting([quit_loop = run_loop_.QuitClosure()](
                                     const fuchsia::intl::Profile& profile) {
        EXPECT_EQ(kPrimaryTimeZoneName,
                  FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                      profile));
        quit_loop.Run();
      }));

  property_provider_.SetTimeZones({kPrimaryTimeZoneName});
  property_provider_.NotifyChange();

  run_loop_.Run();
}

TEST_F(IntlProfileWatcherTest, ChangeNotification_BeforeInitialization) {
  property_provider_.SetTimeZones({kPrimaryTimeZoneName});
  property_provider_.NotifyChange();

  auto watcher = CreateIntlProfileWatcher(
      base::BindLambdaForTesting([quit_loop = run_loop_.QuitClosure()](
                                     const fuchsia::intl::Profile& profile) {
        EXPECT_EQ(kPrimaryTimeZoneName,
                  FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                      profile));
        quit_loop.Run();
      }));

  run_loop_.Run();
}

// Ensure no crash when the peer service cannot be reached during creation.
TEST_F(IntlProfileWatcherTest, ChannelClosedBeforeCreation) {
  base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
  EXPECT_CALL(callback, Run(testing::_)).Times(0);

  property_provider_.Close();

  auto watcher = CreateIntlProfileWatcher(callback.Get());

  property_provider_.NotifyChange();
  run_loop_.RunUntilIdle();
}

// Ensure no crash when the channel is closed after creation.
TEST_F(IntlProfileWatcherTest, ChannelClosedAfterCreation) {
  base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
  EXPECT_CALL(callback, Run(testing::_)).Times(0);

  auto watcher = CreateIntlProfileWatcher(callback.Get());

  property_provider_.Close();

  property_provider_.NotifyChange();
  run_loop_.RunUntilIdle();
}

TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, NoZones) {
  EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                    fuchsia::intl::Profile()));
}

TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, EmptyZonesList) {
  EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                    CreateProfileWithTimeZones({})));
}

TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, OneZone) {
  EXPECT_EQ(kPrimaryTimeZoneName,
            FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                CreateProfileWithTimeZones({kPrimaryTimeZoneName})));
}

TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, TwoZones) {
  EXPECT_EQ(kPrimaryTimeZoneName,
            FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
                CreateProfileWithTimeZones(
                    {kPrimaryTimeZoneName, kSecondaryTimeZoneName})));
}

TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, NoLocales) {
  EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
                    fuchsia::intl::Profile()));
}

TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, EmptyLocalesList) {
  EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
                    CreateProfileWithLocales({})));
}

TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, OneLocale) {
  EXPECT_EQ(kPrimaryLocaleName,
            FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
                CreateProfileWithLocales({kPrimaryLocaleName})));
}

TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, MultipleLocales) {
  EXPECT_EQ(kPrimaryLocaleName,
            FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
                CreateProfileWithLocales(
                    {kPrimaryLocaleName, kSecondaryLocaleName})));
}

}  // namespace base