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

#include "components/sync_device_info/device_info_sync_bridge.h"

#include <algorithm>
#include <map>
#include <set>
#include <utility>

#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/chromeos_buildflags.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/time.h"
#include "components/sync/model/data_batch.h"
#include "components/sync/model/data_type_activation_request.h"
#include "components/sync/model/entity_change.h"
#include "components/sync/model/metadata_batch.h"
#include "components/sync/protocol/device_info_specifics.pb.h"
#include "components/sync/protocol/entity_data.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync/protocol/sync_enums.pb.h"
#include "components/sync/test/mock_model_type_change_processor.h"
#include "components/sync/test/model_type_store_test_util.h"
#include "components/sync/test/test_matchers.h"
#include "components/sync_device_info/device_info_prefs.h"
#include "components/sync_device_info/device_info_util.h"
#include "components/sync_device_info/local_device_info_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chromeos/system/fake_statistics_provider.h"
#endif

namespace syncer {
namespace {

using sync_pb::DeviceInfoSpecifics;
using sync_pb::EntitySpecifics;
using sync_pb::ModelTypeState;
using testing::_;
using testing::AllOf;
using testing::InvokeWithoutArgs;
using testing::IsEmpty;
using testing::IsNull;
using testing::Matcher;
using testing::NiceMock;
using testing::NotNull;
using testing::Pair;
using testing::Return;
using testing::SizeIs;
using testing::UnorderedElementsAre;

using DeviceCountMap = std::map<sync_pb::SyncEnums_DeviceType, int>;
using DeviceInfoList = std::vector<std::unique_ptr<DeviceInfo>>;
using StorageKeyList = ModelTypeSyncBridge::StorageKeyList;
using RecordList = ModelTypeStore::RecordList;
using StartCallback = ModelTypeControllerDelegate::StartCallback;
using WriteBatch = ModelTypeStore::WriteBatch;

const int kLocalSuffix = 0;

const sync_pb::SyncEnums_DeviceType kLocalDeviceType =
    sync_pb::SyncEnums_DeviceType_TYPE_LINUX;

MATCHER_P(HasDeviceInfo, expected, "") {
  return arg.device_info().SerializeAsString() == expected.SerializeAsString();
}

MATCHER_P(EqualsProto, expected, "") {
  return arg.SerializeAsString() == expected.SerializeAsString();
}

MATCHER_P(ModelEqualsSpecifics, expected_specifics, "") {
  if (expected_specifics.has_sharing_fields() !=
      arg.sharing_info().has_value()) {
    return false;
  }

  if (expected_specifics.has_sharing_fields()) {
    auto& expected_fields = expected_specifics.sharing_fields();
    auto& arg_info = *arg.sharing_info();
    if (expected_fields.vapid_fcm_token() !=
            arg_info.vapid_target_info.fcm_token ||
        expected_fields.vapid_p256dh() != arg_info.vapid_target_info.p256dh ||
        expected_fields.vapid_auth_secret() !=
            arg_info.vapid_target_info.auth_secret ||
        expected_fields.sender_id_fcm_token_v2() !=
            arg_info.sender_id_target_info.fcm_token ||
        expected_fields.sender_id_p256dh_v2() !=
            arg_info.sender_id_target_info.p256dh ||
        expected_fields.sender_id_auth_secret_v2() !=
            arg_info.sender_id_target_info.auth_secret ||
        static_cast<size_t>(expected_fields.enabled_features_size()) !=
            arg_info.enabled_features.size()) {
      return false;
    }

    for (int i = 0; i < expected_fields.enabled_features_size(); ++i) {
      if (!arg_info.enabled_features.count(
              expected_fields.enabled_features(i))) {
        return false;
      }
    }
  }

  ModelTypeSet expected_data_types;
  for (const int field_number :
       expected_specifics.invalidation_fields().interested_data_type_ids()) {
    expected_data_types.Put(GetModelTypeFromSpecificsFieldNumber(field_number));
  }
  if (expected_data_types != arg.interested_data_types()) {
    return false;
  }

  // Note that we ignore the device name here to avoid having to inject the
  // local device's.
  return expected_specifics.cache_guid() == arg.guid() &&
         expected_specifics.device_type() == arg.device_type() &&
         expected_specifics.sync_user_agent() == arg.sync_user_agent() &&
         expected_specifics.chrome_version() == arg.chrome_version() &&
         expected_specifics.signin_scoped_device_id() ==
             arg.signin_scoped_device_id() &&
         expected_specifics.model() == arg.model_name() &&
         expected_specifics.manufacturer() == arg.manufacturer_name() &&
         expected_specifics.feature_fields()
                 .send_tab_to_self_receiving_enabled() ==
             arg.send_tab_to_self_receiving_enabled() &&
         expected_specifics.invalidation_fields().instance_id_token() ==
             arg.fcm_registration_token();
}

Matcher<std::unique_ptr<EntityData>> HasSpecifics(
    const Matcher<sync_pb::EntitySpecifics>& m) {
  return testing::Pointee(testing::Field(&EntityData::specifics, m));
}

MATCHER(HasLastUpdatedAboutNow, "") {
  const sync_pb::DeviceInfoSpecifics& specifics = arg.device_info();
  const base::Time now = base::Time::Now();
  const base::TimeDelta tolerance = base::Minutes(1);
  const base::Time actual_last_updated =
      ProtoTimeToTime(specifics.last_updated_timestamp());

  if (actual_last_updated < now - tolerance) {
    *result_listener << "which is too far in the past";
    return false;
  }
  if (actual_last_updated > now + tolerance) {
    *result_listener << "which is too far in the future";
    return false;
  }
  return true;
}

MATCHER(HasInstanceIdToken, "") {
  const sync_pb::DeviceInfoSpecifics& specifics = arg.device_info();
  if (specifics.invalidation_fields().instance_id_token().empty()) {
    *result_listener << "which is empty";
    return false;
  }
  return true;
}

MATCHER(HasAnyInterestedDataTypes, "") {
  const sync_pb::DeviceInfoSpecifics& specifics = arg.device_info();
  if (specifics.invalidation_fields().interested_data_type_ids().empty()) {
    *result_listener << "which is empty";
    return false;
  }
  return true;
}

std::string CacheGuidForSuffix(int suffix) {
  return base::StringPrintf("cache guid %d", suffix);
}

std::string ClientNameForSuffix(int suffix) {
  return base::StringPrintf("client name %d", suffix);
}

std::string SyncUserAgentForSuffix(int suffix) {
  return base::StringPrintf("sync user agent %d", suffix);
}

std::string ChromeVersionForSuffix(int suffix) {
  return base::StringPrintf("chrome version %d", suffix);
}

std::string SigninScopedDeviceIdForSuffix(int suffix) {
  return base::StringPrintf("signin scoped device id %d", suffix);
}

LocalDeviceNameInfo GetLocalDeviceNameInfoBlocking() {
  base::RunLoop run_loop;
  LocalDeviceNameInfo info;
  GetLocalDeviceNameInfo(base::BindLambdaForTesting(
      [&](LocalDeviceNameInfo local_device_name_info) {
        info = std::move(local_device_name_info);
        run_loop.Quit();
      }));
  run_loop.Run();
  return info;
}

std::string ModelForSuffix(int suffix) {
  return base::StringPrintf("model %d", suffix);
}

std::string ManufacturerForSuffix(int suffix) {
  return base::StringPrintf("manufacturer %d", suffix);
}

std::string SharingVapidFcmTokenForSuffix(int suffix) {
  return base::StringPrintf("sharing vapid fcm token %d", suffix);
}

std::string SharingVapidP256dhForSuffix(int suffix) {
  return base::StringPrintf("sharing vapid p256dh %d", suffix);
}

std::string SharingVapidAuthSecretForSuffix(int suffix) {
  return base::StringPrintf("sharing vapid auth secret %d", suffix);
}

std::string SharingSenderIdFcmTokenForSuffix(int suffix) {
  return base::StringPrintf("sharing sender-id fcm token %d", suffix);
}

std::string SharingSenderIdP256dhForSuffix(int suffix) {
  return base::StringPrintf("sharing sender-id p256dh %d", suffix);
}

std::string SharingSenderIdAuthSecretForSuffix(int suffix) {
  return base::StringPrintf("sharing sender-id auth secret %d", suffix);
}

sync_pb::SharingSpecificFields::EnabledFeatures SharingEnabledFeaturesForSuffix(
    int suffix) {
  return suffix % 2 ? sync_pb::SharingSpecificFields::CLICK_TO_CALL_V2
                    : sync_pb::SharingSpecificFields::SHARED_CLIPBOARD_V2;
}

std::string SyncInvalidationsInstanceIdTokenForSuffix(int suffix) {
  return base::StringPrintf("instance id token %d", suffix);
}

ModelTypeSet SyncInvalidationsInterestedDataTypes() {
  return ModelTypeSet(BOOKMARKS);
}

DataTypeActivationRequest TestDataTypeActivationRequest(SyncMode sync_mode) {
  DataTypeActivationRequest request;
  request.cache_guid = CacheGuidForSuffix(kLocalSuffix);
  request.sync_mode = sync_mode;
  return request;
}

DeviceInfoSpecifics CreateSpecifics(
    int suffix,
    base::Time last_updated = base::Time::Now()) {
  DeviceInfoSpecifics specifics;
  specifics.set_cache_guid(CacheGuidForSuffix(suffix));
  specifics.set_client_name(ClientNameForSuffix(suffix));
  specifics.set_device_type(kLocalDeviceType);
  specifics.set_sync_user_agent(SyncUserAgentForSuffix(suffix));
  specifics.set_chrome_version(ChromeVersionForSuffix(suffix));
  specifics.set_signin_scoped_device_id(SigninScopedDeviceIdForSuffix(suffix));
  specifics.set_model(ModelForSuffix(suffix));
  specifics.set_manufacturer(ManufacturerForSuffix(suffix));
  specifics.set_full_hardware_class("");
  specifics.set_last_updated_timestamp(TimeToProtoTime(last_updated));
  specifics.mutable_feature_fields()->set_send_tab_to_self_receiving_enabled(
      true);
  specifics.mutable_sharing_fields()->set_vapid_fcm_token(
      SharingVapidFcmTokenForSuffix(suffix));
  specifics.mutable_sharing_fields()->set_vapid_p256dh(
      SharingVapidP256dhForSuffix(suffix));
  specifics.mutable_sharing_fields()->set_vapid_auth_secret(
      SharingVapidAuthSecretForSuffix(suffix));
  specifics.mutable_sharing_fields()->set_sender_id_fcm_token_v2(
      SharingSenderIdFcmTokenForSuffix(suffix));
  specifics.mutable_sharing_fields()->set_sender_id_p256dh_v2(
      SharingSenderIdP256dhForSuffix(suffix));
  specifics.mutable_sharing_fields()->set_sender_id_auth_secret_v2(
      SharingSenderIdAuthSecretForSuffix(suffix));
  specifics.mutable_sharing_fields()->add_enabled_features(
      SharingEnabledFeaturesForSuffix(suffix));

  specifics.mutable_invalidation_fields()->set_instance_id_token(
      SyncInvalidationsInstanceIdTokenForSuffix(suffix));
  for (const ModelType type : SyncInvalidationsInterestedDataTypes()) {
    specifics.mutable_invalidation_fields()->add_interested_data_type_ids(
        GetSpecificsFieldNumberFromModelType(type));
  }

  return specifics;
}

ModelTypeState StateWithEncryption(const std::string& encryption_key_name) {
  ModelTypeState state;
  state.set_initial_sync_done(true);
  state.set_cache_guid(CacheGuidForSuffix(kLocalSuffix));
  state.set_encryption_key_name(encryption_key_name);
  return state;
}

// Creates an EntityData around a copy of the given specifics.
EntityData SpecificsToEntity(const DeviceInfoSpecifics& specifics) {
  EntityData data;
  *data.specifics.mutable_device_info() = specifics;
  return data;
}

std::string CacheGuidToTag(const std::string& guid) {
  return "DeviceInfo_" + guid;
}

// Helper method to reduce duplicated code between tests. Wraps the given
// specifics objects in an EntityData and EntityChange of type ACTION_ADD, and
// returns an EntityChangeList containing them all. Order is maintained.
EntityChangeList EntityAddList(
    const std::vector<DeviceInfoSpecifics>& specifics_list) {
  EntityChangeList changes;
  for (const auto& specifics : specifics_list) {
    changes.push_back(EntityChange::CreateAdd(specifics.cache_guid(),
                                              SpecificsToEntity(specifics)));
  }
  return changes;
}

std::map<std::string, sync_pb::EntitySpecifics> DataBatchToSpecificsMap(
    std::unique_ptr<DataBatch> batch) {
  std::map<std::string, sync_pb::EntitySpecifics> storage_key_to_specifics;
  while (batch && batch->HasNext()) {
    auto [key, data] = batch->Next();
    storage_key_to_specifics[key] = data->specifics;
  }
  return storage_key_to_specifics;
}

class TestLocalDeviceInfoProvider : public MutableLocalDeviceInfoProvider {
 public:
  TestLocalDeviceInfoProvider() = default;

  TestLocalDeviceInfoProvider(const TestLocalDeviceInfoProvider&) = delete;
  TestLocalDeviceInfoProvider& operator=(const TestLocalDeviceInfoProvider&) =
      delete;

  ~TestLocalDeviceInfoProvider() override = default;

  // MutableLocalDeviceInfoProvider implementation.
  void Initialize(
      const std::string& cache_guid,
      const std::string& session_name,
      const std::string& manufacturer_name,
      const std::string& model_name,
      const std::string& full_hardware_class,
      std::unique_ptr<DeviceInfo> device_info_restored_from_store) override {
    std::string last_fcm_registration_token;
    ModelTypeSet last_interested_data_types;
    if (device_info_restored_from_store) {
      last_fcm_registration_token =
          device_info_restored_from_store->fcm_registration_token();
      last_interested_data_types =
          device_info_restored_from_store->interested_data_types();
    }

    std::set<sync_pb::SharingSpecificFields::EnabledFeatures>
        sharing_enabled_features{SharingEnabledFeaturesForSuffix(kLocalSuffix)};
    local_device_info_ = std::make_unique<DeviceInfo>(
        cache_guid, session_name, ChromeVersionForSuffix(kLocalSuffix),
        SyncUserAgentForSuffix(kLocalSuffix), kLocalDeviceType,
        SigninScopedDeviceIdForSuffix(kLocalSuffix), manufacturer_name,
        model_name, full_hardware_class, base::Time(),
        DeviceInfoUtil::GetPulseInterval(),
        /*send_tab_to_self_receiving_enabled=*/true,
        DeviceInfo::SharingInfo(
            {SharingVapidFcmTokenForSuffix(kLocalSuffix),
             SharingVapidP256dhForSuffix(kLocalSuffix),
             SharingVapidAuthSecretForSuffix(kLocalSuffix)},
            {SharingSenderIdFcmTokenForSuffix(kLocalSuffix),
             SharingSenderIdP256dhForSuffix(kLocalSuffix),
             SharingSenderIdAuthSecretForSuffix(kLocalSuffix)},
            sharing_enabled_features),
        /*paask_info=*/absl::nullopt, last_fcm_registration_token,
        last_interested_data_types);
  }

  void Clear() override { local_device_info_.reset(); }

  void UpdateClientName(const std::string& client_name) override {
    ASSERT_TRUE(local_device_info_);
    local_device_info_->set_client_name(client_name);
  }

  version_info::Channel GetChannel() const override {
    return version_info::Channel::UNKNOWN;
  }

  MOCK_METHOD(bool, IsUmaEnabledOnCrOSDevice, (), (const));

  const DeviceInfo* GetLocalDeviceInfo() const override {
    if (local_device_info_) {
      if (fcm_registration_token_) {
        local_device_info_->set_fcm_registration_token(
            *fcm_registration_token_);
      }
      if (interested_data_types_) {
        local_device_info_->set_interested_data_types(*interested_data_types_);
      }
      if (paask_info_) {
        auto copy = *paask_info_;
        local_device_info_->set_paask_info(std::move(copy));
      }
    }
    return local_device_info_.get();
  }

  base::CallbackListSubscription RegisterOnInitializedCallback(
      const base::RepeatingClosure& callback) override {
    NOTIMPLEMENTED();
    return {};
  }

  void UpdateFCMRegistrationToken(const std::string& fcm_registration_token) {
    fcm_registration_token_ = fcm_registration_token;
  }

  void UpdateInterestedDataTypes(const ModelTypeSet& data_types) {
    interested_data_types_ = data_types;
  }

  void UpdatePhoneAsASecurityKeyInfo(
      const DeviceInfo::PhoneAsASecurityKeyInfo& paask_info) {
    paask_info_ = paask_info;
  }

 private:
  std::unique_ptr<DeviceInfo> local_device_info_;
  absl::optional<std::string> fcm_registration_token_;
  absl::optional<ModelTypeSet> interested_data_types_;
  absl::optional<DeviceInfo::PhoneAsASecurityKeyInfo> paask_info_;
};  // namespace

class DeviceInfoSyncBridgeTest : public testing::Test,
                                 public DeviceInfoTracker::Observer {
 protected:
  DeviceInfoSyncBridgeTest()
      : store_(ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {
    DeviceInfoPrefs::RegisterProfilePrefs(pref_service_.registry());

#if BUILDFLAG(IS_CHROMEOS_ASH)
    statistics_provider_ =
        std::make_unique<chromeos::system::ScopedFakeStatisticsProvider>();
#endif

    local_device_name_info_ = GetLocalDeviceNameInfoBlocking();
    // By default, mimic a real processor's behavior for IsTrackingMetadata().
    ON_CALL(mock_processor_, ModelReadyToSync)
        .WillByDefault([this](std::unique_ptr<MetadataBatch> batch) {
          ON_CALL(mock_processor_, IsTrackingMetadata())
              .WillByDefault(
                  Return(batch->GetModelTypeState().initial_sync_done()));
        });
  }

  ~DeviceInfoSyncBridgeTest() override {
    // Some tests may never initialize the bridge.
    if (bridge_) {
      bridge_->RemoveObserver(this);
    }

    // Force all remaining (store) tasks to execute so we don't leak memory.
    base::RunLoop().RunUntilIdle();
  }

  DeviceInfoSpecifics CreateLocalDeviceSpecifics(
      const base::Time last_updated = base::Time::Now()) {
    DeviceInfoSpecifics specifics = CreateSpecifics(kLocalSuffix, last_updated);
    specifics.set_model(local_device_name_info_.model_name);
    specifics.set_manufacturer(local_device_name_info_.manufacturer_name);
    return specifics;
  }

  void OnDeviceInfoChange() override { change_count_++; }

  // Initialized the bridge based on the current local device and store.
  void InitializeBridge() {
    auto local_device_info_provider =
        std::make_unique<TestLocalDeviceInfoProvider>();
    // Store a pointer to be able to update the local device info fields.
    local_device_info_provider_ = local_device_info_provider.get();
    bridge_ = std::make_unique<DeviceInfoSyncBridge>(
        std::move(local_device_info_provider),
        ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
        mock_processor_.CreateForwardingProcessor(),
        std::make_unique<DeviceInfoPrefs>(&pref_service_, &clock_));
    bridge_->AddObserver(this);
  }

  // Creates the bridge and runs any outstanding tasks. This will typically
  // cause all initialization callbacks between the sevice and store to fire.
  void InitializeAndPump() {
    InitializeBridge();
    task_environment_.RunUntilIdle();
  }

  void WaitForReadyToSync() {
    ON_CALL(*processor(), IsTrackingMetadata).WillByDefault(Return(true));
    base::RunLoop run_loop;
    EXPECT_CALL(*processor(), ModelReadyToSync)
        .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); }));
    run_loop.Run();
  }

  // Mimics sync being enabled by the user with no remote data. Must be called
  // when the bridge is initialized.
  void EnableSyncAndMergeInitialData(SyncMode sync_mode) {
    DCHECK(bridge_);
    bridge()->OnSyncStarting(TestDataTypeActivationRequest(sync_mode));

    std::unique_ptr<MetadataChangeList> metadata_change_list =
        bridge()->CreateMetadataChangeList();

    metadata_change_list->UpdateModelTypeState(StateWithEncryption(""));
    ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
    bridge()->MergeSyncData(std::move(metadata_change_list),
                            EntityChangeList());
  }

  // Creates the bridge with no prior data on the store, and mimics sync being
  // enabled by the user with no remote data.
  void InitializeAndMergeInitialData(SyncMode sync_mode) {
    InitializeAndPump();
    EnableSyncAndMergeInitialData(sync_mode);
  }

  // Allows access to the store before that will ultimately be used to
  // initialize the bridge.
  ModelTypeStore* store() {
    EXPECT_TRUE(store_);
    return store_.get();
  }

  // Get the number of times the bridge notifies observers of changes.
  int change_count() { return change_count_; }

  TestLocalDeviceInfoProvider* local_device() {
    return local_device_info_provider_;
  }

#if BUILDFLAG(IS_CHROMEOS_ASH)
  chromeos::system::ScopedFakeStatisticsProvider* statistics_provider() {
    EXPECT_TRUE(statistics_provider_);
    return statistics_provider_.get();
  }
#endif

  // Allows access to the bridge after InitializeBridge() is called.
  DeviceInfoSyncBridge* bridge() {
    EXPECT_TRUE(bridge_);
    return bridge_.get();
  }

  MockModelTypeChangeProcessor* processor() { return &mock_processor_; }
  // Should only be called after the bridge has been initialized. Will first
  // recover the bridge's store, so another can be initialized later, and then
  // deletes the bridge.
  void PumpAndShutdown() {
    ASSERT_TRUE(bridge_);
    base::RunLoop().RunUntilIdle();
    bridge_->RemoveObserver(this);
  }

  void RestartBridge() {
    PumpAndShutdown();
    InitializeAndPump();
  }

  void ForcePulse() { bridge()->ForcePulseForTest(); }

  void RefreshLocalDeviceInfo() { bridge()->RefreshLocalDeviceInfoIfNeeded(); }

  void CommitToStoreAndWait(std::unique_ptr<WriteBatch> batch) {
    base::RunLoop loop;
    store()->CommitWriteBatch(
        std::move(batch),
        base::BindOnce(
            [](base::RunLoop* loop, const absl::optional<ModelError>& result) {
              EXPECT_FALSE(result.has_value()) << result->ToString();
              loop->Quit();
            },
            &loop));
    loop.Run();
  }

  void WriteDataToStore(
      const std::vector<DeviceInfoSpecifics>& specifics_list) {
    std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
    for (auto& specifics : specifics_list) {
      batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString());
    }
    CommitToStoreAndWait(std::move(batch));
  }

  void WriteToStoreWithMetadata(
      const std::vector<DeviceInfoSpecifics>& specifics_list,
      ModelTypeState state) {
    std::unique_ptr<WriteBatch> batch = store()->CreateWriteBatch();
    for (auto& specifics : specifics_list) {
      batch->WriteData(specifics.cache_guid(), specifics.SerializeAsString());
    }
    batch->GetMetadataChangeList()->UpdateModelTypeState(state);
    CommitToStoreAndWait(std::move(batch));
  }

  std::map<std::string, DeviceInfoSpecifics> ReadAllFromStore() {
    std::unique_ptr<ModelTypeStore::RecordList> records;
    base::RunLoop loop;
    store()->ReadAllData(base::BindOnce(
        [](std::unique_ptr<ModelTypeStore::RecordList>* output_records,
           base::RunLoop* loop, const absl::optional<syncer::ModelError>& error,
           std::unique_ptr<ModelTypeStore::RecordList> input_records) {
          EXPECT_FALSE(error) << error->ToString();
          EXPECT_THAT(input_records, NotNull());
          *output_records = std::move(input_records);
          loop->Quit();
        },
        &records, &loop));
    loop.Run();
    std::map<std::string, DeviceInfoSpecifics> result;
    if (records) {
      for (const ModelTypeStore::Record& record : *records) {
        DeviceInfoSpecifics specifics;
        EXPECT_TRUE(specifics.ParseFromString(record.value));
        result.emplace(record.id, specifics);
      }
    }
    return result;
  }

  std::map<std::string, sync_pb::EntitySpecifics> GetAllData() {
    base::RunLoop loop;
    std::unique_ptr<DataBatch> batch;
    bridge_->GetAllDataForDebugging(base::BindOnce(
        [](base::RunLoop* loop, std::unique_ptr<DataBatch>* out_batch,
           std::unique_ptr<DataBatch> batch) {
          *out_batch = std::move(batch);
          loop->Quit();
        },
        &loop, &batch));
    loop.Run();
    EXPECT_NE(nullptr, batch);
    return DataBatchToSpecificsMap(std::move(batch));
  }

  std::map<std::string, sync_pb::EntitySpecifics> GetData(
      const std::vector<std::string>& storage_keys) {
    base::RunLoop loop;
    std::unique_ptr<DataBatch> batch;
    bridge_->GetData(storage_keys, base::BindOnce(
                                       [](base::RunLoop* loop,
                                          std::unique_ptr<DataBatch>* out_batch,
                                          std::unique_ptr<DataBatch> batch) {
                                         *out_batch = std::move(batch);
                                         loop->Quit();
                                       },
                                       &loop, &batch));
    loop.Run();
    EXPECT_NE(nullptr, batch);
    return DataBatchToSpecificsMap(std::move(batch));
  }

  const std::string& local_personalizable_name() const {
    return local_device_name_info_.personalizable_name;
  }

  const std::string& local_device_model_name() const {
    return local_device_name_info_.model_name;
  }

 private:
  base::SimpleTestClock clock_;

  int change_count_ = 0;

  // In memory model type store needs to be able to post tasks.
  base::test::TaskEnvironment task_environment_;

  NiceMock<MockModelTypeChangeProcessor> mock_processor_;

  // Holds the store.
  const std::unique_ptr<ModelTypeStore> store_;

  // Stores the local device's name information.
  LocalDeviceNameInfo local_device_name_info_;

  TestingPrefServiceSimple pref_service_;

  // Not initialized immediately (upon test's constructor). This allows each
  // test case to modify the dependencies the bridge will be constructed with.
  std::unique_ptr<DeviceInfoSyncBridge> bridge_;

  raw_ptr<TestLocalDeviceInfoProvider> local_device_info_provider_ = nullptr;

#if BUILDFLAG(IS_CHROMEOS_ASH)
  std::unique_ptr<chromeos::system::ScopedFakeStatisticsProvider>
      statistics_provider_;
#endif
};

TEST_F(DeviceInfoSyncBridgeTest, BeforeSyncEnabled) {
  InitializeBridge();
  EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty());
  base::RunLoop().RunUntilIdle();
  EXPECT_THAT(bridge()->GetAllDeviceInfo(), IsEmpty());
}

TEST_F(DeviceInfoSyncBridgeTest, GetClientTagNormal) {
  InitializeBridge();
  const std::string guid = "abc";
  EntitySpecifics entity_specifics;
  entity_specifics.mutable_device_info()->set_cache_guid(guid);
  EntityData entity_data;
  entity_data.specifics = entity_specifics;
  EXPECT_EQ(CacheGuidToTag(guid), bridge()->GetClientTag(entity_data));
}

TEST_F(DeviceInfoSyncBridgeTest, GetClientTagEmpty) {
  InitializeBridge();
  EntitySpecifics entity_specifics;
  entity_specifics.mutable_device_info();
  EntityData entity_data;
  entity_data.specifics = entity_specifics;
  EXPECT_EQ(CacheGuidToTag(""), bridge()->GetClientTag(entity_data));
}

TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataWithoutMetadata) {
  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  WriteDataToStore({specifics});
  InitializeAndPump();

  // Local data without sync metadata should be thrown away.
  EXPECT_TRUE(ReadAllFromStore().empty());
  EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
}

TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalMetadata) {
  WriteToStoreWithMetadata(std::vector<DeviceInfoSpecifics>(),
                           StateWithEncryption("ekn"));
  InitializeAndPump();

  // Local metadata without data about the local device is corrupt.
  EXPECT_TRUE(ReadAllFromStore().empty());
  EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
}

TEST_F(DeviceInfoSyncBridgeTest, TestWithLocalDataAndMetadata) {
  const DeviceInfoSpecifics local_specifics = CreateLocalDeviceSpecifics();
  ModelTypeState state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({local_specifics}, state);

  EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
                                HasEncryptionKeyName("ekn"),
                                /*entities=*/_)));
  InitializeAndPump();

  EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()),
              ModelEqualsSpecifics(local_specifics));
  EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}

TEST_F(DeviceInfoSyncBridgeTest, TestWithMultipleLocalDataAndMetadata) {
  const DeviceInfoSpecifics local_specifics = CreateLocalDeviceSpecifics();
  const DeviceInfoSpecifics remote_specifics = CreateSpecifics(1);
  ModelTypeState state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({local_specifics, remote_specifics}, state);

  EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
                                HasEncryptionKeyName("ekn"),
                                /*entities=*/_)));
  InitializeAndPump();

  EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  EXPECT_THAT(*bridge()->GetDeviceInfo(local_specifics.cache_guid()),
              ModelEqualsSpecifics(local_specifics));
  EXPECT_THAT(*bridge()->GetDeviceInfo(remote_specifics.cache_guid()),
              ModelEqualsSpecifics(remote_specifics));
}

TEST_F(DeviceInfoSyncBridgeTest, GetData) {
  const DeviceInfoSpecifics local_specifics = CreateLocalDeviceSpecifics();
  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
  WriteToStoreWithMetadata(
      {local_specifics, specifics1, specifics2, specifics3},
      StateWithEncryption("ekn"));
  InitializeAndPump();

  EXPECT_THAT(GetData({specifics1.cache_guid()}),
              UnorderedElementsAre(
                  Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1))));

  EXPECT_THAT(GetData({specifics1.cache_guid(), specifics3.cache_guid()}),
              UnorderedElementsAre(
                  Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
                  Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3))));

  EXPECT_THAT(GetData({specifics1.cache_guid(), specifics2.cache_guid(),
                       specifics3.cache_guid()}),
              UnorderedElementsAre(
                  Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
                  Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2)),
                  Pair(specifics3.cache_guid(), HasDeviceInfo(specifics3))));
}

TEST_F(DeviceInfoSyncBridgeTest, GetDataMissing) {
  InitializeAndPump();
  EXPECT_THAT(GetData({"does_not_exist"}), IsEmpty());
}

TEST_F(DeviceInfoSyncBridgeTest, GetAllData) {
  const DeviceInfoSpecifics local_specifics = CreateLocalDeviceSpecifics();
  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  WriteToStoreWithMetadata({local_specifics, specifics1, specifics2},
                           StateWithEncryption("ekn"));
  InitializeAndPump();

  EXPECT_THAT(GetAllData(),
              UnorderedElementsAre(
                  Pair(local_device()->GetLocalDeviceInfo()->guid(), _),
                  Pair(specifics1.cache_guid(), HasDeviceInfo(specifics1)),
                  Pair(specifics2.cache_guid(), HasDeviceInfo(specifics2))));
}

TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesEmpty) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());

  auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                          EntityChangeList());
  EXPECT_FALSE(error);
  EXPECT_EQ(1, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesInMemory) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());

  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  auto error_on_add = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(), EntityAddList({specifics}));

  EXPECT_FALSE(error_on_add);
  std::unique_ptr<DeviceInfo> info =
      bridge()->GetDeviceInfo(specifics.cache_guid());
  ASSERT_TRUE(info);
  EXPECT_THAT(*info, ModelEqualsSpecifics(specifics));
  EXPECT_EQ(2, change_count());

  syncer::EntityChangeList entity_change_list;
  entity_change_list.push_back(
      EntityChange::CreateDelete(specifics.cache_guid()));
  auto error_on_delete = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(), std::move(entity_change_list));

  EXPECT_FALSE(error_on_delete);
  EXPECT_FALSE(bridge()->GetDeviceInfo(specifics.cache_guid()));
  EXPECT_EQ(3, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesStore) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());

  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  ModelTypeState state = StateWithEncryption("ekn");
  std::unique_ptr<MetadataChangeList> metadata_changes =
      bridge()->CreateMetadataChangeList();
  metadata_changes->UpdateModelTypeState(state);

  auto error = bridge()->ApplySyncChanges(std::move(metadata_changes),
                                          EntityAddList({specifics}));
  EXPECT_FALSE(error);
  EXPECT_EQ(2, change_count());

  EXPECT_CALL(*processor(), ModelReadyToSync(MetadataBatchContains(
                                HasEncryptionKeyName("ekn"),
                                /*entities=*/IsEmpty())));
  RestartBridge();

  std::unique_ptr<DeviceInfo> info =
      bridge()->GetDeviceInfo(specifics.cache_guid());
  ASSERT_TRUE(info);
  EXPECT_THAT(*info, ModelEqualsSpecifics(specifics));
}

TEST_F(DeviceInfoSyncBridgeTest, ApplySyncChangesWithLocalGuid) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());

  ASSERT_TRUE(
      bridge()->GetDeviceInfo(local_device()->GetLocalDeviceInfo()->guid()));
  ASSERT_EQ(1, change_count());

  // The bridge should ignore updates using this specifics because its guid will
  // match the local device.
  EXPECT_CALL(*processor(), Put).Times(0);

  const DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  auto error_on_add = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(), EntityAddList({specifics}));
  EXPECT_FALSE(error_on_add);
  EXPECT_EQ(1, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, ApplyDeleteNonexistent) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());

  syncer::EntityChangeList entity_change_list;
  entity_change_list.push_back(EntityChange::CreateDelete("guid"));
  EXPECT_CALL(*processor(), Delete).Times(0);
  auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                          std::move(entity_change_list));
  EXPECT_FALSE(error);
  EXPECT_EQ(1, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, MergeEmpty) {
  const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix);

  InitializeAndPump();

  ASSERT_FALSE(local_device()->GetLocalDeviceInfo());
  ASSERT_FALSE(bridge()->IsPulseTimerRunningForTest());

  EXPECT_CALL(*processor(), Put(kLocalGuid, _, _));
  EXPECT_CALL(*processor(), Delete).Times(0);

  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));
  ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
  auto error = bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
                                       EntityChangeList());
  EXPECT_FALSE(error);
  EXPECT_EQ(1, change_count());

  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid());
  EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}

TEST_F(DeviceInfoSyncBridgeTest, MergeLocalGuid) {
  const std::string kLocalGuid = CacheGuidForSuffix(kLocalSuffix);

  InitializeAndPump();

  ASSERT_FALSE(local_device()->GetLocalDeviceInfo());

  EXPECT_CALL(*processor(), Put(kLocalGuid, _, _));
  EXPECT_CALL(*processor(), Delete).Times(0);

  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));
  ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
  auto error =
      bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
                              EntityAddList({CreateLocalDeviceSpecifics()}));

  EXPECT_FALSE(error);
  EXPECT_EQ(1, change_count());

  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  EXPECT_EQ(kLocalGuid, local_device()->GetLocalDeviceInfo()->guid());
}

TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevices) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  ON_CALL(*processor(), GetEntityCreationTime)
      .WillByDefault(Return(base::Time::Now()));
  ON_CALL(*processor(), GetEntityModificationTime)
      .WillByDefault(Return(base::Time::Now()));

  // Regardless of the time, these following two ApplySyncChanges(...) calls
  // have the same guid as the local device.
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({CreateLocalDeviceSpecifics()}));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({CreateLocalDeviceSpecifics()}));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  // A different guid will actually contribute to the count.
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({CreateSpecifics(1)}));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 2}}),
            bridge()->CountActiveDevicesByType());

  // Now set time to long ago in the past, it should not be active anymore.
  bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(),
      EntityAddList({CreateSpecifics(1, base::Time::Now() - base::Days(365))}));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());
}

TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithOverlappingTime) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  ASSERT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);

  // Time ranges are overlapping.
  ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(1)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(3)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(2)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(4)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(2)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(5)));

  // With two devices, the local device gets ignored because it doesn't overlap.
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({specifics1, specifics2}));

  ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 2}}),
            bridge()->CountActiveDevicesByType());

  // The third device is also overlapping with the first two (and the local one
  // is still excluded).
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({specifics3}));

  ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 3}}),
            bridge()->CountActiveDevicesByType());
}

TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithNonOverlappingTime) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  ASSERT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  const DeviceInfoSpecifics specifics3 = CreateSpecifics(3);

  // Time ranges are non-overlapping.
  ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(1)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(2)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(3)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(4)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(5)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(6)));

  bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(),
      EntityAddList({specifics1, specifics2, specifics3}));

  ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());
}

TEST_F(DeviceInfoSyncBridgeTest,
       CountActiveDevicesWithNonOverlappingTimeAndDistinctType) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  ASSERT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
  // We avoid TYPE_LINUX below to prevent collisions with the local device,
  // exposed as Linux by LocalDeviceInfoProviderMock.
  specifics1.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_PHONE);
  specifics2.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_CROS);
  specifics3.set_device_type(sync_pb::SyncEnums_DeviceType_TYPE_WIN);

  // Time ranges are non-overlapping.
  ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(1)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(2)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(3)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(4)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(5)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics3.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(6)));

  bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(),
      EntityAddList({specifics1, specifics2, specifics3}));

  ASSERT_EQ(4u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1},
                            {sync_pb::SyncEnums_DeviceType_TYPE_PHONE, 1},
                            {sync_pb::SyncEnums_DeviceType_TYPE_CROS, 1},
                            {sync_pb::SyncEnums_DeviceType_TYPE_WIN, 1}}),
            bridge()->CountActiveDevicesByType());
}

TEST_F(DeviceInfoSyncBridgeTest, CountActiveDevicesWithMalformedTimestamps) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  ASSERT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  const DeviceInfoSpecifics specifics1 = CreateSpecifics(1);
  const DeviceInfoSpecifics specifics2 = CreateSpecifics(2);

  // Time ranges are overlapping.
  ON_CALL(*processor(), GetEntityCreationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(1)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics1.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(4)));
  ON_CALL(*processor(), GetEntityCreationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(3)));
  ON_CALL(*processor(), GetEntityModificationTime(specifics2.cache_guid()))
      .WillByDefault(Return(base::Time::UnixEpoch() + base::Minutes(2)));

  // With two devices, the local device gets ignored because it doesn't overlap.
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({specifics1, specifics2}));

  ASSERT_EQ(3u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());
}

TEST_F(DeviceInfoSyncBridgeTest,
       ShouldFilterOutNonChromeClientsFromDeviceTracker) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  // Local device.
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 1}}),
            bridge()->CountActiveDevicesByType());

  ON_CALL(*processor(), GetEntityCreationTime)
      .WillByDefault(Return(base::Time::Now()));
  ON_CALL(*processor(), GetEntityModificationTime)
      .WillByDefault(Return(base::Time::Now()));

  // A different guid will contribute to the count.
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({CreateSpecifics(1)}));
  ASSERT_THAT(GetAllData(), SizeIs(2));
  ASSERT_THAT(bridge()->GetAllDeviceInfo(), SizeIs(2));
  ASSERT_EQ(DeviceCountMap({{kLocalDeviceType, 2}}),
            bridge()->CountActiveDevicesByType());
  ASSERT_THAT(bridge()->GetDeviceInfo(CacheGuidForSuffix(1)), NotNull());

  // If the Chrome version is not present, it should not be exposed as device.
  sync_pb::DeviceInfoSpecifics specifics2 = CreateSpecifics(2);
  specifics2.clear_chrome_version();
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({specifics2}));
  ASSERT_THAT(GetAllData(), SizeIs(3));
  EXPECT_THAT(bridge()->GetAllDeviceInfo(), SizeIs(2));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 2}}),
            bridge()->CountActiveDevicesByType());
  EXPECT_THAT(bridge()->GetDeviceInfo(CacheGuidForSuffix(2)), IsNull());

  // If only the non-legacy field is present, the device should still be exposed
  // in DeviceInfoTracker.
  sync_pb::DeviceInfoSpecifics specifics3 = CreateSpecifics(3);
  specifics3.clear_chrome_version();
  specifics3.mutable_chrome_version_info()->set_version_number("someversion");
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityAddList({specifics3}));
  ASSERT_THAT(GetAllData(), SizeIs(4));
  EXPECT_THAT(bridge()->GetAllDeviceInfo(), SizeIs(3));
  EXPECT_EQ(DeviceCountMap({{kLocalDeviceType, 3}}),
            bridge()->CountActiveDevicesByType());
  EXPECT_THAT(bridge()->GetDeviceInfo(CacheGuidForSuffix(3)), NotNull());
}

TEST_F(DeviceInfoSyncBridgeTest, SendLocalData) {
  // Ensure |last_updated| is about now, plus or minus a little bit.
  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  InitializeAndMergeInitialData(SyncMode::kFull);
  EXPECT_EQ(1, change_count());
  testing::Mock::VerifyAndClearExpectations(processor());

  // Ensure |last_updated| is about now, plus or minus a little bit.
  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  ForcePulse();
  EXPECT_EQ(2, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithClearData) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(1, change_count());
  ASSERT_FALSE(ReadAllFromStore().empty());
  ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest());

  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                          EntityAddList({specifics}));

  ASSERT_FALSE(error);
  ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(2, change_count());

  // Should clear out all local data and notify observers.
  bridge()->ApplyStopSyncChanges(bridge()->CreateMetadataChangeList());
  EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(3, change_count());
  EXPECT_TRUE(ReadAllFromStore().empty());
  EXPECT_FALSE(bridge()->IsPulseTimerRunningForTest());

  // Reloading from storage shouldn't contain remote data.
  RestartBridge();
  EXPECT_EQ(0u, bridge()->GetAllDeviceInfo().size());

  // If sync is re-enabled and the remote data is now empty, we shouldn't
  // contain remote data.
  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));
  ON_CALL(*processor(), IsTrackingMetadata()).WillByDefault(Return(true));
  bridge()->MergeSyncData(bridge()->CreateMetadataChangeList(),
                          EntityChangeList());
  // Local device.
  EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}

TEST_F(DeviceInfoSyncBridgeTest, ApplyStopSyncChangesWithKeepData) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(1, change_count());
  ASSERT_FALSE(ReadAllFromStore().empty());
  ASSERT_TRUE(bridge()->IsPulseTimerRunningForTest());

  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  auto error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                          EntityAddList({specifics}));

  ASSERT_FALSE(error);
  ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(2, change_count());

  // Should clear out all local data and notify observers.
  bridge()->ApplyStopSyncChanges(/*delete_metadata_change_list=*/nullptr);
  EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  EXPECT_EQ(2, change_count());
  EXPECT_FALSE(ReadAllFromStore().empty());
  EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());

  // Reloading from storage should still contain remote data.
  RestartBridge();
  EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  EXPECT_TRUE(bridge()->IsPulseTimerRunningForTest());
}

TEST_F(DeviceInfoSyncBridgeTest, ExpireOldEntriesUponStartup) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(1, change_count());
  ASSERT_FALSE(ReadAllFromStore().empty());

  const DeviceInfoSpecifics specifics_old =
      CreateSpecifics(1, base::Time::Now() - base::Days(57));
  const DeviceInfoSpecifics specifics_fresh =
      CreateSpecifics(1, base::Time::Now() - base::Days(55));
  auto error = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(),
      EntityAddList({specifics_old, specifics_fresh}));

  ASSERT_FALSE(error);
  ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  ASSERT_EQ(2, change_count());

  // Reloading from storage should expire the old remote entity (but keep the
  // fresh one).
  RestartBridge();
  EXPECT_EQ(2u, bridge()->GetAllDeviceInfo().size());
  // Make sure this is well persisted to the DB store.
  EXPECT_THAT(ReadAllFromStore(),
              UnorderedElementsAre(
                  Pair(local_device()->GetLocalDeviceInfo()->guid(), _),
                  Pair(specifics_fresh.cache_guid(), _)));
}

TEST_F(DeviceInfoSyncBridgeTest, RefreshLocalDeviceInfo) {
  // Ensure |last_updated| is about now, plus or minus a little bit.
  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  InitializeAndMergeInitialData(SyncMode::kFull);
  EXPECT_EQ(1, change_count());
  testing::Mock::VerifyAndClearExpectations(processor());

  // Check that the device is not updated if nothing has been changed.
  RefreshLocalDeviceInfo();
  EXPECT_EQ(1, change_count());

  // Ensure |last_updated| is about now, plus or minus a little bit.
  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  ASSERT_THAT(local_device()->GetLocalDeviceInfo()->fcm_registration_token(),
              IsEmpty());
  local_device()->UpdateFCMRegistrationToken(
      SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix));
  RefreshLocalDeviceInfo();
  EXPECT_EQ(2, change_count());

  // Setting Phone-as-a-security-key fields should trigger an update.
  ASSERT_FALSE(local_device()->GetLocalDeviceInfo()->paask_info().has_value());
  DeviceInfo::PhoneAsASecurityKeyInfo paask_info;
  paask_info.tunnel_server_domain = 123;
  paask_info.contact_id = {1, 2, 3, 4};
  paask_info.secret = {5, 6, 7, 8};
  paask_info.id = 321;
  paask_info.peer_public_key_x962 = {10, 11, 12, 13};
  local_device()->UpdatePhoneAsASecurityKeyInfo(paask_info);

  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  RefreshLocalDeviceInfo();
  EXPECT_EQ(3, change_count());

  // Rotating the PaaSK key should not trigger an update. These fields depend
  // on the current time and are only updated when an update would be sent
  // anyway.
  paask_info.secret = {9, 10, 11, 12};
  paask_info.id = 322;
  local_device()->UpdatePhoneAsASecurityKeyInfo(paask_info);
  RefreshLocalDeviceInfo();
  EXPECT_EQ(3, change_count());

  // But updating other PaaSK fields does trigger an update.
  paask_info.tunnel_server_domain = 124;
  local_device()->UpdatePhoneAsASecurityKeyInfo(paask_info);
  EXPECT_CALL(*processor(), Put(_, HasSpecifics(HasLastUpdatedAboutNow()), _));
  RefreshLocalDeviceInfo();
  EXPECT_EQ(4, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, DeviceNameForTransportOnlySyncMode) {
  InitializeAndMergeInitialData(SyncMode::kTransportOnly);
  ASSERT_EQ(1, change_count());
  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());

  EXPECT_EQ(local_device_model_name(),
            local_device()->GetLocalDeviceInfo()->client_name());
}

TEST_F(DeviceInfoSyncBridgeTest, DeviceNameForFullSyncMode) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1, change_count());
  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());

  EXPECT_EQ(local_personalizable_name(),
            local_device()->GetLocalDeviceInfo()->client_name());
}

// Tests local client name when device is initially synced with transport only
// sync mode, but the sync mode is not available after restart since it is not
// persisted.
TEST_F(DeviceInfoSyncBridgeTest,
       DeviceNameForTransportOnlySyncMode_RestartBridge) {
  std::string expected_device_name = local_device_model_name();
  InitializeAndMergeInitialData(SyncMode::kTransportOnly);

  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  ASSERT_EQ(expected_device_name,
            local_device()->GetLocalDeviceInfo()->client_name());

  EXPECT_CALL(*processor(),
              Put(local_device()->GetLocalDeviceInfo()->guid(), _, _))
      .Times(0);
  RestartBridge();
  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  EXPECT_EQ(expected_device_name,
            local_device()->GetLocalDeviceInfo()->client_name());
}

// Tests local client name when device is initially synced with full sync mode,
// but the sync mode is not available after restart since it is not persisted.
TEST_F(DeviceInfoSyncBridgeTest, DeviceNameForFullSyncMode_RestartBridge) {
  std::string expected_device_name = local_personalizable_name();
  InitializeAndMergeInitialData(SyncMode::kFull);

  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  ASSERT_EQ(expected_device_name,
            local_device()->GetLocalDeviceInfo()->client_name());

  EXPECT_CALL(*processor(),
              Put(local_device()->GetLocalDeviceInfo()->guid(), _, _))
      .Times(0);
  RestartBridge();
  ASSERT_TRUE(local_device()->GetLocalDeviceInfo());
  EXPECT_EQ(expected_device_name,
            local_device()->GetLocalDeviceInfo()->client_name());
}

TEST_F(DeviceInfoSyncBridgeTest, RefreshLocalDeviceNameForSyncModeToggle) {
  std::string expected_device_name_full_sync = local_personalizable_name();
  std::string expected_device_name_transport_only = local_device_model_name();

  // Initialize with full sync mode.
  InitializeAndMergeInitialData(SyncMode::kFull);
  const syncer::DeviceInfo* device = local_device()->GetLocalDeviceInfo();

  ASSERT_TRUE(device);
  ASSERT_EQ(expected_device_name_full_sync, device->client_name());

  // Toggle to transport only sync mode.
  bridge()->ApplyStopSyncChanges(/*delete_metadata_change_list=*/nullptr);
  bridge()->OnSyncStarting(
      TestDataTypeActivationRequest(SyncMode::kTransportOnly));

  device = local_device()->GetLocalDeviceInfo();
  ASSERT_TRUE(device);
  ASSERT_EQ(expected_device_name_transport_only, device->client_name());

  // Toggle to full sync mode.
  bridge()->ApplyStopSyncChanges(/*delete_metadata_change_list=*/nullptr);
  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));

  device = local_device()->GetLocalDeviceInfo();
  ASSERT_TRUE(device);
  ASSERT_EQ(expected_device_name_full_sync, device->client_name());
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldSendInvalidationFields) {
  EXPECT_CALL(*processor(),
              Put(_,
                  HasSpecifics(
                      AllOf(HasInstanceIdToken(), HasAnyInterestedDataTypes())),
                  _));
  InitializeAndPump();
  local_device()->UpdateFCMRegistrationToken(
      SyncInvalidationsInstanceIdTokenForSuffix(kLocalSuffix));
  local_device()->UpdateInterestedDataTypes(
      SyncInvalidationsInterestedDataTypes());
  EnableSyncAndMergeInitialData(SyncMode::kFull);
}

TEST_F(DeviceInfoSyncBridgeTest,
       ShouldNotifyWhenAdditionalInterestedDataTypesSynced) {
  InitializeAndPump();
  local_device()->UpdateInterestedDataTypes({syncer::BOOKMARKS});
  EnableSyncAndMergeInitialData(SyncMode::kFull);

  base::MockRepeatingCallback<void(const ModelTypeSet&)> callback;
  bridge()->SetCommittedAdditionalInterestedDataTypesCallback(callback.Get());
  local_device()->UpdateInterestedDataTypes(
      {syncer::BOOKMARKS, syncer::SESSIONS});

  bridge()->RefreshLocalDeviceInfoIfNeeded();

  const std::string guid = local_device()->GetLocalDeviceInfo()->guid();
  EXPECT_CALL(*processor(), IsEntityUnsynced(guid)).WillOnce(Return(false));

  EXPECT_CALL(callback, Run(ModelTypeSet(syncer::SESSIONS)));
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityChangeList());
}

TEST_F(DeviceInfoSyncBridgeTest,
       ShouldNotNotifyWithoutAdditionalInterestedDataTypes) {
  InitializeAndPump();
  local_device()->UpdateInterestedDataTypes(
      {syncer::BOOKMARKS, syncer::SESSIONS});
  EnableSyncAndMergeInitialData(SyncMode::kFull);

  base::MockRepeatingCallback<void(const ModelTypeSet&)> callback;
  bridge()->SetCommittedAdditionalInterestedDataTypesCallback(callback.Get());
  local_device()->UpdateInterestedDataTypes({syncer::BOOKMARKS});

  bridge()->RefreshLocalDeviceInfoIfNeeded();

  std::string guid = local_device()->GetLocalDeviceInfo()->guid();
  EXPECT_CALL(*processor(), IsEntityUnsynced(guid)).WillOnce(Return(false));
  EXPECT_CALL(callback, Run).Times(0);
  bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                             EntityChangeList());
}

// This test mimics the case when OnSyncStarting is called before the metadata
// is loaded from the storage.
TEST_F(DeviceInfoSyncBridgeTest,
       ShouldCleanUpMetadataOnInvalidCacheGuidAfterReadMetadata) {
  const std::string kInvalidCacheGuid = "invalid_cache_guid";

  DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  specifics.set_cache_guid(kInvalidCacheGuid);

  ModelTypeState model_type_state = StateWithEncryption("ekn");
  model_type_state.set_cache_guid(kInvalidCacheGuid);

  WriteToStoreWithMetadata({specifics}, model_type_state);

  InitializeBridge();
  ASSERT_FALSE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  ASSERT_FALSE(bridge()->IsRecentLocalCacheGuid(kInvalidCacheGuid));

  EXPECT_CALL(*processor(), IsTrackingMetadata).WillRepeatedly(Return(false));
  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));

  EXPECT_TRUE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  EXPECT_FALSE(bridge()->IsRecentLocalCacheGuid(kInvalidCacheGuid));
  EXPECT_THAT(bridge()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo(),
              IsNull());

  base::RunLoop run_loop;
  EXPECT_CALL(*processor(), ModelReadyToSync)
      .WillOnce([this, &run_loop](std::unique_ptr<MetadataBatch> batch) {
        // Mimic CTBMTP behaviour on invalid cache GUID.
        std::unique_ptr<MetadataChangeList> change_list =
            bridge()->CreateMetadataChangeList();
        change_list->ClearMetadata(CacheGuidForSuffix(kLocalSuffix));
        change_list->ClearModelTypeState();
        bridge()->ApplyStopSyncChanges(std::move(change_list));

        bridge()->OnSyncStarting(
            TestDataTypeActivationRequest(SyncMode::kFull));

        run_loop.Quit();
      });
  run_loop.Run();

  EXPECT_TRUE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  EXPECT_FALSE(bridge()->IsRecentLocalCacheGuid(kInvalidCacheGuid));
  EXPECT_THAT(bridge()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo(),
              IsNull());
}

// This test mimics the opposite case when OnSyncStarting is called after the
// metadata is loaded from the storage.
TEST_F(DeviceInfoSyncBridgeTest,
       ShouldCleanUpMetadataOnInvalidCacheGuidAfterSyncStarting) {
  const std::string kInvalidCacheGuid = "invalid_cache_guid";

  DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  specifics.set_cache_guid(kInvalidCacheGuid);

  ModelTypeState model_type_state = StateWithEncryption("ekn");
  model_type_state.set_cache_guid(kInvalidCacheGuid);

  WriteToStoreWithMetadata({specifics}, model_type_state);
  InitializeBridge();
  ASSERT_FALSE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  ASSERT_FALSE(bridge()->IsRecentLocalCacheGuid(kInvalidCacheGuid));

  // Wait until the metadata is loaded.
  base::RunLoop run_loop;
  EXPECT_CALL(*processor(), IsTrackingMetadata).WillOnce(Return(true));
  EXPECT_CALL(*processor(), ModelReadyToSync)
      .WillOnce([&run_loop](std::unique_ptr<MetadataBatch> batch) {
        run_loop.Quit();
      });
  run_loop.Run();

  EXPECT_FALSE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  EXPECT_TRUE(bridge()->IsRecentLocalCacheGuid(kInvalidCacheGuid));
  EXPECT_THAT(bridge()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo(),
              NotNull());

  // Mimic CTBMTP behaviour on invalid cache GUID.
  EXPECT_CALL(*processor(), IsTrackingMetadata).WillRepeatedly(Return(false));
  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));

  std::unique_ptr<MetadataChangeList> change_list =
      bridge()->CreateMetadataChangeList();
  change_list->ClearMetadata(CacheGuidForSuffix(kLocalSuffix));
  change_list->ClearModelTypeState();
  bridge()->ApplyStopSyncChanges(std::move(change_list));

  bridge()->OnSyncStarting(TestDataTypeActivationRequest(SyncMode::kFull));

  // Check that the cache GUID is in recent devices.
  EXPECT_TRUE(
      bridge()->IsRecentLocalCacheGuid(CacheGuidForSuffix(kLocalSuffix)));
  EXPECT_THAT(bridge()->GetLocalDeviceInfoProvider()->GetLocalDeviceInfo(),
              IsNull());
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldInvokeCallbackOnReadAllMetadata) {
  const DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  const ModelTypeState model_type_state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({specifics}, model_type_state);

  InitializeBridge();
  // Wait until the metadata is loaded.
  base::RunLoop run_loop;
  EXPECT_CALL(*processor(), IsTrackingMetadata).WillOnce(Return(true));
  EXPECT_CALL(*processor(), ModelReadyToSync)
      .WillOnce([&run_loop](std::unique_ptr<MetadataBatch> batch) {
        run_loop.Quit();
      });
  run_loop.Run();

  // Check that the bridge won't call SendLocalData() during initial merge.
  EXPECT_CALL(*processor(), Put).Times(0);

  // Check that the bridge has notified observers even if the local data hasn't
  // been changed.
  EXPECT_EQ(1, change_count());
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldRemoveDeviceInfoOnTombstone) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  const DeviceInfoSpecifics specifics = CreateSpecifics(1);
  absl::optional<ModelError> error = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(), EntityAddList({specifics}));
  ASSERT_FALSE(error);
  ASSERT_EQ(2u, bridge()->GetAllDeviceInfo().size());

  EntityChangeList changes;
  changes.push_back(EntityChange::CreateDelete(specifics.cache_guid()));
  error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                     std::move(changes));
  ASSERT_FALSE(error);

  EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
  EXPECT_NE(bridge()->GetAllDeviceInfo().front()->guid(),
            specifics.cache_guid());
}

TEST_F(DeviceInfoSyncBridgeTest,
       ShouldReuploadOnceAfterLocalDeviceInfoTombstone) {
  InitializeAndMergeInitialData(SyncMode::kFull);
  ASSERT_EQ(1u, bridge()->GetAllDeviceInfo().size());

  EntityChangeList changes;
  changes.push_back(
      EntityChange::CreateDelete(CacheGuidForSuffix(kLocalSuffix)));

  // An incoming deletion for the local device info should result in a reupload.
  // The reupload should only be triggered once, to prevent any possible
  // ping-pong between devices.
  EXPECT_CALL(*processor(), Put(CacheGuidForSuffix(kLocalSuffix), _, _));
  absl::optional<ModelError> error = bridge()->ApplySyncChanges(
      bridge()->CreateMetadataChangeList(), std::move(changes));
  ASSERT_FALSE(error);

  // The local device info should still exist.
  EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());

  changes.clear();
  changes.push_back(
      EntityChange::CreateDelete(CacheGuidForSuffix(kLocalSuffix)));
  error = bridge()->ApplySyncChanges(bridge()->CreateMetadataChangeList(),
                                     std::move(changes));
  ASSERT_FALSE(error);

  EXPECT_EQ(1u, bridge()->GetAllDeviceInfo().size());
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldForceLocalDeviceInfoUpload) {
  const DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  const ModelTypeState model_type_state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({specifics}, model_type_state);

  InitializeBridge();

  bridge()->ForcePulseForTest();

  // Check that the bridge calls SendLocalData() during initialization.
  EXPECT_CALL(*processor(), Put);

  WaitForReadyToSync();
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldNotUploadRecentLocalDeviceUponStartup) {
  const DeviceInfoSpecifics specifics = CreateLocalDeviceSpecifics();
  const ModelTypeState model_type_state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({specifics}, model_type_state);

  InitializeBridge();

  // Check that the bridge doesn't call SendLocalData() during initialization
  // (because the local device info has recent last update time).
  EXPECT_CALL(*processor(), Put).Times(0);

  WaitForReadyToSync();
}

TEST_F(DeviceInfoSyncBridgeTest, ShouldUploadOutdatedLocalDeviceInfo) {
  // Create an outdated local device info which should be reuploaded during
  // initialization.
  const DeviceInfoSpecifics specifics =
      CreateLocalDeviceSpecifics(base::Time::Now() - base::Days(10));
  const ModelTypeState model_type_state = StateWithEncryption("ekn");
  WriteToStoreWithMetadata({specifics}, model_type_state);

  InitializeBridge();

  // Check that the bridge calls SendLocalData() during initialization.
  EXPECT_CALL(*processor(), Put);

  WaitForReadyToSync();
}

}  // namespace

}  // namespace syncer


text:
'use strict';

xdescribe('lib/symboltracker', function() {
    // TODO: don't worry about tests until the following issue has been resolved:
    // https://github.com/hegemonic/jsdoc-baseline/issues/88
});

redpajama_set_name: RedPajamaGithub


text:
[![Code Health](https://landscape.io/github/wmv/cli-sms/master/landscape.svg?style=flat-square)](https://landscape.io/github/wmv/cli-sms/master)

#Installation instructions:

(Requires a valid Nexmo account with credit/trial)
- Download zip & extract
- From within the downloaded folder, run ```pip install --editable .``` (don't forget the period at the end.)
- Make sure the environment variables for ```NEXMO_API_KEY``` and ```NEXMO_API_SECRET``` have been set.

The values for those are available in your Nexmo dashboard. To set, run:

 ```echo export NEXMO_API_KEY=YourNexmoApiKeyHere >> ~/.bash_profile``` (replace YourNexmoApiKeyHere with actual value)

 and

 ```echo export NEXMO_API_KEY=YourNexmoApiSecretHere >> ~/.bash_profile``` (replace YourNexmoApiSecretHere with actual value)

- Optionally, to set a default recipient number (for when you choose to ommit the --number parameter; please note this is not required), run this:

 ```echo export SMS_MESSAGE_DEFAULT_RECIPIENT=YourCellNumberOfChoice >> ~/.bash_profile``` (replace YourCellNumberOfChoice with actual value)

The instructions above assume you are using bash, but feel free to replace bash_profile with the equivalent for your shell of choice.

#Usage: ```sms``` [OPTIONS]

#Options:
 - ```--number``` TEXT  This is the phone number that the SMS will be sent to.
 - ```--text``` TEXT    This is the text that will form the body of the SMS.
 - ```--verbose```      Set this option to see extra details.
 - ```-v```             Set this option to see extra details.
 - ```--help```         Show help information and exit.


text:
BookmarkSubMenuModel::BookmarkSubMenuModel(
    ui::SimpleMenuModel::Delegate* delegate, Browser* browser)
    : SimpleMenuModel(delegate) {
  Build(browser);
}

BookmarkSubMenuModel::~BookmarkSubMenuModel() {}

void BookmarkSubMenuModel::Build(Browser* browser) {
  AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR);
  AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER);
#if !defined(OS_CHROMEOS)
  AddItemWithStringId(IDC_IMPORT_SETTINGS, IDS_IMPORT_SETTINGS_MENU_LABEL);
#endif
  if (delegate()->IsCommandIdVisible(IDC_BOOKMARK_PAGE) ||
      delegate()->IsCommandIdVisible(IDC_BOOKMARK_ALL_TABS) ||
      delegate()->IsCommandIdVisible(IDC_PIN_TO_START_SCREEN)) {
    AddSeparator(ui::NORMAL_SEPARATOR);

    AddItemWithStringId(IDC_BOOKMARK_PAGE, IDS_BOOKMARK_THIS_PAGE);
    AddItemWithStringId(IDC_BOOKMARK_ALL_TABS, IDS_BOOKMARK_OPEN_PAGES);

    AddItemWithStringId(IDC_PIN_TO_START_SCREEN, IDS_PIN_TO_START_SCREEN);
  }
#if defined(OS_MACOSX)
  AddSeparator(ui::NORMAL_SEPARATOR);
#endif
}


text:
package org.kaazing.gateway.client.impl.ws;

import org.kaazing.gateway.client.impl.CommandMessage;
import org.kaazing.gateway.client.impl.WebSocketChannel;
import org.kaazing.gateway.client.impl.WebSocketHandler;
import org.kaazing.gateway.client.impl.WebSocketHandlerAdapter;
import org.kaazing.gateway.client.impl.WebSocketHandlerListener;
import org.kaazing.gateway.client.impl.util.WSURI;
import org.kaazing.gateway.client.util.WrappedByteBuffer;

import java.util.logging.Level;
import java.util.logging.Logger;

/*
 * WebSocket Handler Chain
 * WebSocket - CompoisteHandler
 *                |- {SelctedHandler} - NativeHandler (see nativeHandler chain)
 *                   |- {SelectedHandler} - EmulatedHandler (see emulatedHandler chain)
 * Responsibilities:
 *     a). change Channel readyState when events are received
 *     b). fire events only necessary
 *
 * TODO:
 *         n/a
 */
public class WebSocketSelectedHandlerImpl extends WebSocketHandlerAdapter implements WebSocketSelectedHandler {
    private static final String CLASS_NAME = WebSocketSelectedHandlerImpl.class.getName();
    private static final Logger LOG = Logger.getLogger(CLASS_NAME);

    public static interface  WebSocketSelectedHandlerFactory {
        WebSocketSelectedHandler createSelectedHandler();
    }

    static WebSocketSelectedHandlerFactory FACTORY = WebSocketSelectedHandlerImpl::new;

    protected WebSocketHandlerListener listener;

    public WebSocketSelectedHandlerImpl() {
        LOG.entering(CLASS_NAME, "<init>");
    }

    /**
     * Establishes the websocket connection
     */
    @Override
    public void processConnect(WebSocketChannel channel, WSURI uri, String[] protocols) {
        LOG.entering(CLASS_NAME, "connect", channel);

        if (((WebSocketSelectedChannel)channel).readyState == ReadyState.CLOSED) {
            throw new IllegalStateException("WebSocket is already closed");
        }

        nextHandler.processConnect(channel, uri, protocols);
    }

    @Override
    public void processClose(WebSocketChannel channel, int code, String reason) {
        LOG.entering(CLASS_NAME, "processDisconnect");
        WebSocketSelectedChannel ch = (WebSocketSelectedChannel)channel;
        if (ch.readyState == ReadyState.OPEN || ch.readyState == ReadyState.CONNECTING) {
            ch.readyState = ReadyState.CLOSING;
            nextHandler.processClose(channel, code, reason);
        }
    }

    public void handleConnectionOpened(WebSocketChannel channel, String protocol) {
        LOG.entering(CLASS_NAME, "handleConnectionOpened");

        WebSocketSelectedChannel selectedChannel = (WebSocketSelectedChannel)channel;
        if (selectedChannel.readyState == ReadyState.CONNECTING) {
            selectedChannel.readyState = ReadyState.OPEN;
            listener.connectionOpened(channel, protocol);
        }
    }

    public void handleBinaryMessageReceived(WebSocketChannel channel, WrappedByteBuffer message) {
        LOG.entering(CLASS_NAME, "handleMessageReceived", message);

        if (((WebSocketSelectedChannel)channel).readyState != ReadyState.OPEN) {
            return;
        }

        if (LOG.isLoggable(Level.FINEST)) {
            LOG.log(Level.FINEST, message.getHexDump());
        }

        listener.binaryMessageReceived(channel, message);
    }

    public void handleTextMessageReceived(WebSocketChannel channel, String message) {
        LOG.entering(CLASS_NAME, "handleTextMessageReceived", message);

        if (((WebSocketSelectedChannel)channel).readyState != ReadyState.OPEN) {
            return;
        }

        if (LOG.isLoggable(Level.FINEST)) {
            LOG.log(Level.FINEST, message);
        }

        listener.textMessageReceived(channel, message);
    }

    protected void handleConnectionClosed(WebSocketChannel channel, boolean wasClean, int code, String reason) {
        LOG.entering(CLASS_NAME, "handleConnectionClosed");

        WebSocketSelectedChannel selectedChannel = (WebSocketSelectedChannel)channel;
        if (selectedChannel.readyState != ReadyState.CLOSED) {
            selectedChannel.readyState = ReadyState.CLOSED;
            listener.connectionClosed(channel, wasClean, code, reason);
        }
    }

    protected void handleConnectionClosed(WebSocketChannel channel, Exception ex) {
        LOG.entering(CLASS_NAME, "handleConnectionClosed");

        WebSocketSelectedChannel selectedChannel = (WebSocketSelectedChannel)channel;
        if (selectedChannel.readyState != ReadyState.CLOSED) {
            selectedChannel.readyState = ReadyState.CLOSED;
            listener.connectionClosed(channel, ex);
        }
    }

    protected void handleConnectionFailed(WebSocketChannel channel, Exception ex) {
        LOG.entering(CLASS_NAME, "connectionFailed");

        WebSocketSelectedChannel selectedChannel = (WebSocketSelectedChannel)channel;
        if (selectedChannel.readyState != ReadyState.CLOSED) {
            selectedChannel.readyState = ReadyState.CLOSED;
            listener.connectionFailed(channel, ex);
        }
    }

    @Override
    public void setNextHandler(WebSocketHandler nextHandler) {
        this.nextHandler = nextHandler;

        nextHandler.setListener(new WebSocketHandlerListener() {

            @Override
            public void connectionOpened(WebSocketChannel channel, String protocol) {
                handleConnectionOpened(channel, protocol);
            }

            @Override
            public void binaryMessageReceived(WebSocketChannel channel, WrappedByteBuffer message) {
                handleBinaryMessageReceived(channel, message);
            }

            @Override
            public void textMessageReceived(WebSocketChannel channel, String message) {
                handleTextMessageReceived(channel, message);
            }

            @Override
            public void connectionClosed(WebSocketChannel channel, boolean wasClean, int code, String reason) {
                handleConnectionClosed(channel, wasClean, code, reason);
            }

            @Override
            public void connectionClosed(WebSocketChannel channel, Exception ex) {
                handleConnectionClosed(channel, ex);
            }

            @Override
            public void connectionFailed(WebSocketChannel channel, Exception ex) {
                handleConnectionFailed(channel, ex);
            }

            @Override
            public void redirected(WebSocketChannel channel, String location) {
            }

            @Override
            public void authenticationRequested(WebSocketChannel channel, String location, String challenge) {
            }

            @Override
            public void commandMessageReceived(WebSocketChannel channel, CommandMessage message) {
            }

        });
    }

    public void setListener(WebSocketHandlerListener listener) {
        this.listener = listener;
    }

}


text:
..
      Licensed under the Apache License, Version 2.0 (the "License"); you may
      not use this file except in compliance with the License. You may obtain
      a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
      WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
      License for the specific language governing permissions and limitations
      under the License.

:tocdepth: 2

======================
Image Service Versions
======================

.. rest_expand_all::

.. include:: versions.inc

Version History
***************

**Newton changes**

- version 2.4 is CURRENT
- version 2.3 is SUPPORTED
- version 1.1 is DEPRECATED
- version 1.0 is DEPRECATED

**Kilo changes**

- version 2.3 is CURRENT
- version 1.1 is SUPPORTED

**Havana changes**

- version 2.2 is CURRENT
- version 2.1 is SUPPORTED

**Grizzly changes**

- version 2.1 is CURRENT
- version 2.0 is SUPPORTED

**Folson changes**

- version 2.0 is CURRENT

**Diablo changes**

- version 1.1 is CURRENT
- version 1.0 is SUPPORTED

**Bexar changes**

- version 1.0 is CURRENT


text:
/**
 * \date 2014
 * \author Jan Issac (jan.issac@gmail.com)
 * Max-Planck-Institute for Intelligent Systems, University of Southern California
 */

#include <gtest/gtest.h>

#include <fl/exception/exception.hpp>
#include <fl/filter/gaussian/transform/point_set.hpp>

TEST(Exception, create)
{
    struct FirstWeight
    {
        double w;
        std::string name;
    };

    typedef fl::PointSet<Eigen::Matrix<double, 1, 1>, -1> SigmaPointGaussian;
    SigmaPointGaussian sigmas(1);

    try
    {
        sigmas.point(0, Eigen::Matrix<double, 1, 1>::Random(), {1.23, 1.24});
        sigmas.point(1, Eigen::Matrix<double, 1, 1>::Random(), {1.23, 1.24});
        sigmas.point(2, Eigen::Matrix<double, 1, 1>::Random(), {1.23, 1.24});
    }
    catch(fl::OutOfBoundsException& e) { }
    catch(...)
    {
        ADD_FAILURE();
    }
}

TEST(Exception, OutOfBoundsException_default_construction)
{
    fl::OutOfBoundsException e;
    EXPECT_NE(
        std::string(e.what()).find("Index out of bounds"),
        std::string::npos);

//    try {
//        fl_throw(fl::OutOfBoundsException());
//    } catch (fl::Exception& e) {
//        std::cout << e.what() << std::endl;
//    }
}

TEST(Exception, OutOfBoundsException_index)
{
    fl::OutOfBoundsException e(10);
    EXPECT_NE(
        std::string(e.what()).find("Index[10] out of bounds"),
        std::string::npos);

//    try {
//        fl_throw(e);
//    } catch (fl::Exception& e) {
//        std::cout << e.what() << std::endl;
//    }
}

TEST(Exception, OutOfBoundsException_index_size)
{
    fl::OutOfBoundsException e(10, 8);
    EXPECT_NE(
        std::string(e.what()).find("Index[10] out of bounds [0, 8)"),
        std::string::npos);

//    try {
//        fl_throw(e);
//    } catch (fl::Exception& e) {
//        std::cout << e.what() << std::endl;
//    }
}


text:
FactoryGirl.define do
  factory :guest_application do
  end
end


text:
using CERental.Core.Enums;
using System.Collections.Generic;

namespace CERental.Core.Domain
{
    public class Equipment : Entity
    {
        public EquipmentType Type { get; set; }
        public string Name { get; set; }

        /* Navigational properties */
        public virtual ICollection<EquipmentInRent> EquipmentInRent { get; set; }
    }
}


text:
@interface SettingsMultilineDetailItem : TableViewItem

// The main text string.
@property(nonatomic, copy) NSString* text;

// The detail text string.
@property(nonatomic, copy) NSString* detailText;

@end

// SettingsMultilineDetailCell implements an TableViewCell
// subclass containing two text labels: a "main" label and a "detail" label.
// The two labels are laid out on top of each other and can span on multiple
// lines. This is to be used with a SettingsMultilineDetailItem.
@interface SettingsMultilineDetailCell : TableViewCell

// UILabels corresponding to |text| and |detailText| from the item.
@property(nonatomic, readonly, strong) UILabel* textLabel;
@property(nonatomic, readonly, strong) UILabel* detailTextLabel;

@end

#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_SETTINGS_MULTILINE_DETAIL_ITEM_H_


text:
using System.Collections.Generic;
using NBitcoin;
using Stratis.SmartContracts.CLR.ContractLogging;
using Stratis.SmartContracts.CLR.Serialization;
using Stratis.SmartContracts.Core.Receipts;
using Stratis.SmartContracts.Core.State;
using Stratis.SmartContracts.Core.State.AccountAbstractionLayer;

namespace Stratis.SmartContracts.CLR
{
    public interface IState
    {
        IBlock Block { get; }
        BalanceState BalanceState { get; }
        IStateRepository ContractState { get; }
        IList<Log> GetLogs(IContractPrimitiveSerializer serializer);
        IReadOnlyList<TransferInfo> InternalTransfers { get; }
        IContractLogHolder LogHolder { get; }
        IState Snapshot();
        NonceGenerator NonceGenerator { get; }
        void TransitionTo(IState state);
        void AddInternalTransfer(TransferInfo transferInfo);
        ulong GetBalance(uint160 address);
        uint160 GenerateAddress(IAddressGenerator addressGenerator);
        ISmartContractState CreateSmartContractState(IState state, RuntimeObserver.IGasMeter gasMeter, uint160 address, BaseMessage message, IStateRepository repository);
        void AddInitialTransfer(TransferInfo initialTransfer);
    }
}


text:
=begin

= Api::BaseController

  - version:  6.343
  - author:   Steve A.

  Base API v2 controller.

=end
class Api::BaseController < ActionController::API
  include ActionController::MimeResponds

  acts_as_token_authentication_handler_for User,
    fallback: false,
    if: ->(controller) { controller.user_token_authenticable? }

  protected

  # Makes sure that the format for the request is an accepted one.
  def ensure_format
    unless request.format.json?
      render( status: 406, json: { success: false, message: I18n.t("api.errors.request_must_be_json") } )
      return
    end
  end
  #-- -------------------------------------------------------------------------
  #++

  # Returns true if  and  does:
  # - the current (sibling) controller does actually respond to JSON
  # - a User instance exists associated with the required key parameter for the API (the +email+ field)
  # - the found User instance responds to token_authenticable?
  #
  def user_token_authenticable?
    # This ensures the token can be used only for JSON requests:
    return false unless request.format.json?
    return false if tokenized_user_identifier.blank?

    # `nil` is still a falsy value, but we want a strictly boolean field here
    tokenized_user.try(:authentication_token?) || false
  end
  #-- -------------------------------------------------------------------------
  #++

  private

  # Returns the first User instance matched by the identifier or nil when not found.
  #
  def tokenized_user
    User.where( email: tokenized_user_identifier.to_s ).first
  end

  # Returns the user indetifier parameter requested by the API.
  #
  def tokenized_user_identifier
    # Customize this based on Simple Token Authentication settings
    request.headers['X-User-Email'] || params[:user_email]
  end
  #-- -------------------------------------------------------------------------
  #++
end


text:
<!DOCTYPE html>

    <meta charset="utf-8">

    <title>Ajuda</title>
    <meta name="description" content="Suporte Aplicativo Du Cheff">
    <meta name="author" content="Jean Fabricio">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="shortcut icon" href="{{url('favicon.ico')}}"/>
    <link href="{{url('plugins/bootstrap/bootstrap.css')}}" rel="stylesheet">
    <link href="{{url('css/font-awesome.min.css')}}" rel="stylesheet">

Ajuda

Selecione abaixo qual assunto você deseja tratar.

	         Como visualizar e atualizar meus dados?

Ver Perfil

Clique no ícone (  )
	        			selecione a opção 'Meu Perfil' no menu lateral.

Altere as informações que você julgar necessárias

clique no botão 'Atualizar' no final da página.

	        	Caso nenhuma informação seja modificada a seguinte mensagem será exibida: 'Nenhum dado foi alterado!',
	        	se a atualização ocorrer com sucesso a seguinte mensagem será exibida: 'Os dados foram atualizados com sucesso!'.

	        	IMPORTANTE: O Macarrão Du Cheff utiliza dos dados dos clientes para fazer as entregas com qualidade
	        		e rapidez, é importante que seus dados sejam sempre atualizados.

	          Esqueci meu Login e Senha

	        	Você pode resgatar seu login e senha recebendo em seu email, para isso
	        	siga os passos abaixo:

Navegue até a tela de 'Login'

Clique em 'Recuperar informações'

Preencha seu Email e clique em 'Recuperar'

Um email será encaminhado para você com seu login e senha redefinidos

	        	Importante: Altere seu login e senha assim que possível

	          Manter-se sempre logado

	        	O Macarrão Du Cheff cria uma sessão de acesso privado e seguro para seus clientes, uma vez logado
	        	os seus dados são armazenados somente no seu aparelho, assim você não precisará ficar efetuando
	        	a login para cada vez que desejar utilizar o Aplicativo Du Cheff.

	          Como fazer o Logout?

Logout

Navegue para a tela 'Home' do aplicativo.

Clique no ícone .

	          Como visualizar e acompanhar meus pedidos?

	        	O Macarrão Du Cheff permite que você acompanhe seu pedido desde o envio até a entrega em sua residência.

Acompanhar Pedido

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			selecione a opção 'Meus Pedidos' no menu lateral.

Selecione o pedido que você deseja visualizar.

Cancelar Pedido

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			selecione a opção 'Meus Pedidos' no menu lateral.

Selecione o pedido que você deseja visualizar.

Clique no botão 'Cancelar Pedido' no final da página.

Confirme o cancelamento clicando na opção 'Sim'

Ver Detalhes dos Pratos

Na tela 'Acompanhamento' após selecionar um pedido clique sobre
	        		o prato na lista 'Detalhes do Pedido' e uma caixa de dialogo irá
	        		exibir os detalhes do prato selecionado.

	        	Todas as informações são carregadas apenas uma única vez, para visualizar novas atualizações
	        		volte para a tela 'Home' e selecione seu pedido novamente, e as atualizações serão exibidas.

	          Como adicionar um pedido em Minhas Prefêrencias?

Adicionar Pedido as Preferências

Na tela de 'Pedidos' clique no ícone (  )
	        			no canto superior à direita, ou clique no botão 'Menu' de seu aparelho.

Selecione Adicionar Preferência

Visualizando Minhas Preferências

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			Selecione a opção 'Preferências'

Selecione uma preferência

	        	Após selecionar uma de suas preferências ela será carregada na tela
	        	pedidos, caso deseje poderá adicionar um novo item ao seu pedido.

	          Reclamações, Elogios, Sugestões

	        	O Macarrão Du Cheff busca oferecer o melhor serviço de atendimento para seus clientes, você poderá
	        	enviar ou sua reclamação, elogio ou sugestão no próprio aplicativo.

Enviando Sugestões

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			Selecione a opção 'Sugestões'

Selecione o Tipo de Mensagem para Sugestão

Após preencher sua mensagem clique no botão 'Enviar'

Enviando Reclamações

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			Selecione a opção 'Sugestões'

Selecione o Tipo de Mensagem para Reclamação

Após preencher sua mensagem clique no botão 'Enviar'

Enviando Elogios

Navegue para a tela 'Home' do aplicativo.

Clique no ícone (  )
	        			Selecione a opção 'Sugestões'

Selecione o Tipo de Mensagem para Elogio

Após preencher sua mensagem clique no botão 'Enviar'

	        	O cliente receberá uma mensagem em seu Email com nossa resposta.

	          Como fazer um Pedido?

Acessando a tela de Pedidos

Navegue para a tela 'Home' do aplicativo.

Clique no botão 'Fazer Pedido'

Adicionando uma Bebida ao seu pedido

	        			Na tela de Pedido clique no botão 'Adicionar Bebida'
	        			na categoria bebidas (Faixa Amarelo).

	        			Após clicar você será direcionado para a tela de Bebidas, selecione uma bebida
	        			de sua preferência, caso não encontre você poderá utilizar a pesquisa
	        			clicando no ícone (  ) no canto superior
	        			à direita.

Ao clicar sobre a bebida você será direcionado para o detalhes da bebida,
	        			informe a quantidade em unidades que você deseja adicionar.

Para finaliar clique no botão 'Adicionar bebida'

Adicionando um Prato ao seu pedido

	        			Na tela de Pedido clique no botão 'Adicionar Prato'
	        			na categoria pratos (Faixa Vermelho Escuro).

	        			Após clicar você será direcionado para a tela de Cardápio, selecione uma categoria,
	        			caso não encontre você poderá utilizar a pesquisa clicando no ícone
	        			 (  ) no canto superior
	        			à direita.

Ao clicar sobre o cardápio você será direcionado para a lista de pratos
	        			da categoria do cardápio escolhida. Selecione um prato de sua preferência,
	        			caso não encontre você poderá utilizar a pesquisa clicando no ícone
	        			 (  ) no canto superior
	        			à direita.

Ao clicar sobre o prato você será direcionado para o detalhes da prato,
	        			onde você poderá montar seu prato a seu gosto.

 Veja 'Montando meu Prato'

Montando meu Prato

Na tela de Detalhes do prato, após selecionar a categoria do cardápio
	        			e o prato, você visualizará todas as variedades e adicionais do prato
	        			escolhido.

Selecione as variedades que deseja misturar em seu prato.

Em seguida, selecione os adicionais como acompanhamentos de seu
	        			prato segundo os limites de adicionais para cada categoria.

Após montar seu prato selecione a quantidade de unidades e
	        			clique no botão 'Adicionar Prato' no final da página.

Finalizando Pedido

Você poderá informar ao Cheff alguma restrição a seu gosto, basta
	        			inserir sua observação no pedido, no final da página.

Para finalizar seu pedido clique no botão 'Finalizar'

Limpar Pedido

Para você limpar os itens de seu pedido afim de começar um
	        			novo clique no ícone (  ) no canto superior
	        			direito.

Removendo um item específico

Para remover um item específico do seu pedido como bebida ou prato
	        			clique no ícone (  ) no canto direito
	        			do item.

Visualizando detalhes do prato

Para visualizar os detalhes de seu prato em seu pedido clique sobre o item
	        			prato e uma caixa de dialogo irá exibir as variedades e adicionais do
	        			prato selecionado.

	        	Observações: Seu pedido é salvo no carrinho, assim mesmo depois de finalizar o
	        		aplicativo todos os itens que você começou a montar em seu pedido ficarão
	        		salvos para que você possa concluir seu pedido depois.


text:
package com.gzlk.android.isp.model.organization;

import com.gzlk.android.isp.model.Model;

import java.util.ArrayList;

/**
 * 功能描述:组织列表里简化的组织以及小组、成员信息

 * 创建作者:Hsiang Leekwok

 * 创建时间:2017/07/12 14:20

 * 作者邮箱:xiang.l.g@gmail.com

 * 最新版本:Version: 1.0.0

 * 修改时间:2017/07/12 14:20

 * 修改人员:

 * 修改备注:

 */

public class SimpleGroup extends Model {

    private String name;
    private int type;
    private ArrayList<SimpleMember> memberList;
    private ArrayList<SimpleSquad> squadList;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public ArrayList<SimpleMember> getMemberList() {
        return memberList;
    }

    public void setMemberList(ArrayList<SimpleMember> memberList) {
        this.memberList = memberList;
    }

    public ArrayList<SimpleSquad> getSquadList() {
        return squadList;
    }

    public void setSquadList(ArrayList<SimpleSquad> squadList) {
        this.squadList = squadList;
    }
}


text:
<?php

namespace TheSeer\phpDox\Generator\Enricher {

    class EnricherException extends \Exception {

        const LoadError = 1;
    }

}


text:
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $, window, Mustache */

/**
 * Inline widget to display WebPlatformDocs JSON data nicely formatted
 */
define(function (require, exports, module) {
    'use strict';

    // Load Brackets modules
    var InlineWidget    = brackets.getModule("editor/InlineWidget").InlineWidget,
        ExtensionUtils  = brackets.getModule("utils/ExtensionUtils"),
        Strings         = brackets.getModule("strings"),
        NativeApp       = brackets.getModule("utils/NativeApp");

    // Load template
    var inlineEditorTemplate = require("text!InlineDocsViewer.html");

    // Load CSS
    ExtensionUtils.loadStyleSheet(module, "WebPlatformDocs.less");

    /**
     * @param {!string} cssPropName
     * @param {!{SUMMARY:string, URL:string, VALUES:Array.<{TITLE:string, DESCRIPTION:string}>}} cssPropDetails
     */
    function InlineDocsViewer(cssPropName, cssPropDetails) {
        InlineWidget.call(this);

        var propValues = cssPropDetails.VALUES.map(function (valueInfo) {
            return { value: valueInfo.TITLE, description: valueInfo.DESCRIPTION };
        });

        var templateVars = {
            propName    : cssPropName,
            summary     : cssPropDetails.SUMMARY,
            propValues  : propValues,
            url         : cssPropDetails.URL,
            Strings     : Strings
        };

        var html = Mustache.render(inlineEditorTemplate, templateVars);

        this.$wrapperDiv = $(html);
        this.$htmlContent.append(this.$wrapperDiv);

        // Preprocess link tags to make URLs absolute
        this.$wrapperDiv.find("a").each(function (index, elem) {
            var $elem = $(elem);
            var url = $elem.attr("href");
            if (url && url.substr(0, 4) !== "http") {
                // URLs in JSON data are relative
                url = "http://docs.webplatform.org" + (url.charAt(0) !== "/" ? "/" : "") + url;
                $elem.attr("href", url);
            }
            $elem.attr("title", url);
        });

        this._sizeEditorToContent   = this._sizeEditorToContent.bind(this);
        this._handleLinkClick       = this._handleLinkClick.bind(this);
        this._handleWheelScroll     = this._handleWheelScroll.bind(this);

        this.$wrapperDiv.on("click", "a", this._handleLinkClick);
        this.$wrapperDiv.find(".scroller").on("mousewheel", this._handleWheelScroll);
    }

    InlineDocsViewer.prototype = Object.create(InlineWidget.prototype);
    InlineDocsViewer.prototype.constructor = InlineDocsViewer;
    InlineDocsViewer.prototype.parentClass = InlineWidget.prototype;

    InlineDocsViewer.prototype.$wrapperDiv = null;

    /** Clicking any link should open it in browser, not in Brackets shell */
    InlineDocsViewer.prototype._handleLinkClick = function (event) {
        var url = $(event.currentTarget).attr("href");
        if (url) {
            NativeApp.openURLInDefaultBrowser(url);
        }
    };

    /** Don't allow scrollwheel/trackpad to bubble up to host editor - makes scrolling docs painful */
    InlineDocsViewer.prototype._handleWheelScroll = function (event) {
        var scrollingUp = (event.originalEvent.wheelDeltaY > 0),
            scroller = event.currentTarget;

        // If content has no scrollbar, let host editor scroll normally
        if (scroller.clientHeight >= scroller.scrollHeight) {
            return;
        }

        // We need to block the event from both the host CodeMirror code (by stopping bubbling) and the
        // browser's native behavior (by preventing default). We preventDefault() *only* when the docs
        // scroller is at its limit (when an ancestor would get scrolled instead); otherwise we'd block
        // normal scrolling of the docs themselves.
        event.stopPropagation();
        if (scrollingUp && scroller.scrollTop === 0) {
            event.preventDefault();
        } else if (!scrollingUp && scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight) {
            event.preventDefault();
        }
    };

    InlineDocsViewer.prototype.onAdded = function () {
        InlineDocsViewer.prototype.parentClass.onAdded.apply(this, arguments);

        // Set height initially, and again whenever width might have changed (word wrap)
        this._sizeEditorToContent();
        $(window).on("resize", this._sizeEditorToContent);

        // Set focus
        this.$wrapperDiv.find(".scroller")[0].focus();
    };

    InlineDocsViewer.prototype.onClosed = function () {
        InlineDocsViewer.prototype.parentClass.onClosed.apply(this, arguments);

        $(window).off("resize", this._sizeEditorToContent);
    };

    InlineDocsViewer.prototype._sizeEditorToContent = function () {
        this.hostEditor.setInlineWidgetHeight(this, this.$wrapperDiv.height() + 20, true);
    };

    module.exports = InlineDocsViewer;
});