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

#include "services/preferences/pref_store_impl.h"

#include <utility>

#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/values.h"
#include "components/prefs/value_map_pref_store.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/preferences/public/cpp/pref_store_client.h"
#include "services/preferences/public/interfaces/preferences.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::Invoke;
using testing::WithoutArgs;

namespace prefs {
namespace {

class PrefStoreObserverMock : public PrefStore::Observer {
 public:
  MOCK_METHOD1(OnPrefValueChanged, void(const std::string&));
  MOCK_METHOD1(OnInitializationCompleted, void(bool));
};

class MockPrefStore : public ValueMapPrefStore {
 public:
  bool IsInitializationComplete() const override {
    return initialized_ && success_;
  }

  void AddObserver(PrefStore::Observer* observer) override {
    observers_.AddObserver(observer);
  }

  void RemoveObserver(PrefStore::Observer* observer) override {
    observers_.RemoveObserver(observer);
  }

  void CompleteInitialization(bool success) {
    initialized_ = true;
    success_ = success;
    for (auto& observer : observers_) {
      // Some pref stores report completing initialization more than once. Test
      // that additional calls are ignored.
      observer.OnInitializationCompleted(success);
      observer.OnInitializationCompleted(success);
    }
  }

  void SetValue(const std::string& key,
                std::unique_ptr<base::Value> value,
                uint32_t flags) override {
    ValueMapPrefStore::SetValue(key, std::move(value), flags);
    for (auto& observer : observers_) {
      observer.OnPrefValueChanged(key);
    }
  }

 private:
  ~MockPrefStore() override = default;

  bool initialized_ = false;
  bool success_ = false;
  base::ObserverList<PrefStore::Observer, true> observers_;
};

constexpr char kKey[] = "path.to.key";
constexpr char kOtherKey[] = "path.to.other_key";

void ExpectInitializationComplete(PrefStore* pref_store, bool success) {
  PrefStoreObserverMock observer;
  pref_store->AddObserver(&observer);
  base::RunLoop run_loop;
  EXPECT_CALL(observer, OnPrefValueChanged("")).Times(0);
  EXPECT_CALL(observer, OnInitializationCompleted(success))
      .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
  run_loop.Run();
  pref_store->RemoveObserver(&observer);
}

void ExpectPrefChange(PrefStore* pref_store, base::StringPiece key) {
  PrefStoreObserverMock observer;
  pref_store->AddObserver(&observer);
  base::RunLoop run_loop;
  EXPECT_CALL(observer, OnPrefValueChanged(key.as_string()))
      .WillOnce(WithoutArgs(Invoke([&run_loop]() { run_loop.Quit(); })));
  run_loop.Run();
  pref_store->RemoveObserver(&observer);
}

class PrefStoreImplTest : public testing::Test {
 public:
  PrefStoreImplTest() = default;

  // testing::Test:
  void TearDown() override {
    pref_store_ = nullptr;
    base::RunLoop().RunUntilIdle();
    impl_.reset();
    base::RunLoop().RunUntilIdle();
  }

  void CreateImpl(
      scoped_refptr<PrefStore> backing_pref_store,
      std::vector<std::string> observed_prefs = std::vector<std::string>()) {
    impl_ = base::MakeUnique<PrefStoreImpl>(std::move(backing_pref_store));

    if (observed_prefs.empty())
      observed_prefs.insert(observed_prefs.end(), {kKey, kOtherKey});
    pref_store_ = make_scoped_refptr(
        new PrefStoreClient(impl_->AddObserver(observed_prefs)));
  }

  PrefStore* pref_store() { return pref_store_.get(); }

 private:
  base::MessageLoop message_loop_;

  std::unique_ptr<PrefStoreImpl> impl_;

  scoped_refptr<PrefStore> pref_store_;

  DISALLOW_COPY_AND_ASSIGN(PrefStoreImplTest);
};

TEST_F(PrefStoreImplTest, InitializationSuccess) {
  auto backing_pref_store = make_scoped_refptr(new MockPrefStore());
  backing_pref_store->SetValue(kKey, base::MakeUnique<base::Value>("value"), 0);
  CreateImpl(backing_pref_store);
  EXPECT_FALSE(pref_store()->IsInitializationComplete());

  backing_pref_store->CompleteInitialization(true);
  ExpectInitializationComplete(pref_store(), true);
  EXPECT_TRUE(pref_store()->IsInitializationComplete());
}

TEST_F(PrefStoreImplTest, InitializationFailure) {
  auto backing_pref_store = make_scoped_refptr(new MockPrefStore());
  backing_pref_store->SetValue(kKey, base::MakeUnique<base::Value>("value"), 0);
  CreateImpl(backing_pref_store);
  EXPECT_FALSE(pref_store()->IsInitializationComplete());

  backing_pref_store->CompleteInitialization(false);
  ExpectInitializationComplete(pref_store(), false);

  // TODO(sammc): Should IsInitializationComplete() return false?
  EXPECT_TRUE(pref_store()->IsInitializationComplete());
}

TEST_F(PrefStoreImplTest, ValueChangesBeforeInitializationCompletes) {
  auto backing_pref_store = make_scoped_refptr(new MockPrefStore());
  CreateImpl(backing_pref_store);
  EXPECT_FALSE(pref_store()->IsInitializationComplete());

  const base::Value value("value");
  backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
  backing_pref_store->CompleteInitialization(true);

  // The update occurs before initialization has completed, so should not
  // trigger notifications to client observers, but the value should be
  // observable once initialization completes.
  ExpectInitializationComplete(pref_store(), true);
  EXPECT_TRUE(pref_store()->IsInitializationComplete());

  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(value.Equals(output));
}

TEST_F(PrefStoreImplTest, InitialValue) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  const base::Value value("value");
  backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());
  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(value.Equals(output));
}

TEST_F(PrefStoreImplTest, InitialValueWithoutPathExpansion) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  base::DictionaryValue dict;
  dict.SetStringWithoutPathExpansion(kKey, "value");
  backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0);
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());
  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(dict.Equals(output));
}

TEST_F(PrefStoreImplTest, WriteObservedByClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  const base::Value value("value");
  backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);

  ExpectPrefChange(pref_store(), kKey);
  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(value.Equals(output));
}

TEST_F(PrefStoreImplTest, WriteToUnregisteredPrefNotObservedByClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  CreateImpl(backing_pref_store, {kKey});
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  backing_pref_store->SetValue(kOtherKey, base::MakeUnique<base::Value>(123),
                               0);
  backing_pref_store->SetValue(kKey, base::MakeUnique<base::Value>("value"), 0);

  ExpectPrefChange(pref_store(), kKey);
  EXPECT_FALSE(pref_store()->GetValue(kOtherKey, nullptr));
}

TEST_F(PrefStoreImplTest, WriteWithoutPathExpansionObservedByClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  base::DictionaryValue dict;
  dict.SetStringWithoutPathExpansion(kKey, "value");
  backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0);

  ExpectPrefChange(pref_store(), kKey);
  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(dict.Equals(output));
}

TEST_F(PrefStoreImplTest, RemoveObservedByClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  const base::Value value("value");
  backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(value.Equals(output));
  backing_pref_store->RemoveValue(kKey, 0);

  // This should be a no-op and shouldn't trigger a notification for the other
  // client.
  backing_pref_store->RemoveValue(kKey, 0);

  ExpectPrefChange(pref_store(), kKey);
  EXPECT_FALSE(pref_store()->GetValue(kKey, &output));
}

TEST_F(PrefStoreImplTest, RemoveOfUnregisteredPrefNotObservedByClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  const base::Value value("value");
  backing_pref_store->SetValue(kKey, value.CreateDeepCopy(), 0);
  backing_pref_store->SetValue(kOtherKey, value.CreateDeepCopy(), 0);
  CreateImpl(backing_pref_store, {kKey});
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  backing_pref_store->RemoveValue(kOtherKey, 0);
  backing_pref_store->RemoveValue(kKey, 0);

  ExpectPrefChange(pref_store(), kKey);
}

TEST_F(PrefStoreImplTest, RemoveWithoutPathExpansionObservedByOtherClient) {
  auto backing_pref_store = make_scoped_refptr(new ValueMapPrefStore());
  base::DictionaryValue dict;
  dict.SetStringWithoutPathExpansion(kKey, "value");
  backing_pref_store->SetValue(kKey, dict.CreateDeepCopy(), 0);
  CreateImpl(backing_pref_store);
  ASSERT_TRUE(pref_store()->IsInitializationComplete());

  const base::Value* output = nullptr;
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  EXPECT_TRUE(dict.Equals(output));

  base::Value* mutable_value = nullptr;
  dict.SetStringWithoutPathExpansion(kKey, "value");
  ASSERT_TRUE(backing_pref_store->GetMutableValue(kKey, &mutable_value));
  base::DictionaryValue* mutable_dict = nullptr;
  ASSERT_TRUE(mutable_value->GetAsDictionary(&mutable_dict));
  mutable_dict->RemoveWithoutPathExpansion(kKey, nullptr);
  backing_pref_store->ReportValueChanged(kKey, 0);

  ExpectPrefChange(pref_store(), kKey);
  ASSERT_TRUE(pref_store()->GetValue(kKey, &output));
  const base::DictionaryValue* dict_value = nullptr;
  ASSERT_TRUE(output->GetAsDictionary(&dict_value));
  EXPECT_TRUE(dict_value->empty());
}

}  // namespace
}  // namespace prefs
