// Copyright 2012 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/prefs/json_pref_store.h"

#include <stdint.h>

#include <memory>
#include <utility>

#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_samples.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "components/prefs/persistent_pref_store_unittest.h"
#include "components/prefs/pref_filter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace {

const char kHomePage[] = "homepage";

const char kReadJson[] =
    "{\n"
    "  \"homepage\": \"http://www.cnn.com\",\n"
    "  \"some_directory\": \"/usr/local/\",\n"
    "  \"tabs\": {\n"
    "    \"new_windows_in_tabs\": true,\n"
    "    \"max_tabs\": 20\n"
    "  }\n"
    "}";

const char kInvalidJson[] = "!@#$%^&";

// Expected output for tests using RunBasicJsonPrefStoreTest().
const char kWriteGolden[] =
    "{\"homepage\":\"http://www.cnn.com\","
     "\"long_int\":{\"pref\":\"214748364842\"},"
     "\"some_directory\":\"/usr/sbin/\","
     "\"tabs\":{\"max_tabs\":10,\"new_windows_in_tabs\":false}}";

// A PrefFilter that will intercept all calls to FilterOnLoad() and hold on
// to the |prefs| until explicitly asked to release them.
class InterceptingPrefFilter : public PrefFilter {
 public:
  InterceptingPrefFilter();
  InterceptingPrefFilter(OnWriteCallbackPair callback_pair);

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

  ~InterceptingPrefFilter() override;

  // PrefFilter implementation:
  void FilterOnLoad(PostFilterOnLoadCallback post_filter_on_load_callback,
                    base::Value::Dict pref_store_contents) override;
  void FilterUpdate(std::string_view path) override {}
  OnWriteCallbackPair FilterSerializeData(
      base::Value::Dict& pref_store_contents) override {
    return std::move(on_write_callback_pair_);
  }
  void OnStoreDeletionFromDisk() override {}

  bool has_intercepted_prefs() const { return intercepted_prefs_ != nullptr; }

  // Finalize an intercepted read, handing |intercepted_prefs_| back to its
  // JsonPrefStore.
  void ReleasePrefs();

 private:
  PostFilterOnLoadCallback post_filter_on_load_callback_;
  std::unique_ptr<base::Value::Dict> intercepted_prefs_;
  OnWriteCallbackPair on_write_callback_pair_;
};

InterceptingPrefFilter::InterceptingPrefFilter() = default;

InterceptingPrefFilter::InterceptingPrefFilter(
    OnWriteCallbackPair callback_pair) {
  on_write_callback_pair_ = std::move(callback_pair);
}

InterceptingPrefFilter::~InterceptingPrefFilter() = default;

void InterceptingPrefFilter::FilterOnLoad(
    PostFilterOnLoadCallback post_filter_on_load_callback,
    base::Value::Dict pref_store_contents) {
  post_filter_on_load_callback_ = std::move(post_filter_on_load_callback);
  intercepted_prefs_ =
      std::make_unique<base::Value::Dict>(std::move(pref_store_contents));
}

void InterceptingPrefFilter::ReleasePrefs() {
  EXPECT_FALSE(post_filter_on_load_callback_.is_null());
  std::unique_ptr<base::Value::Dict> prefs = std::move(intercepted_prefs_);
  std::move(post_filter_on_load_callback_).Run(std::move(*prefs), false);
}

class MockPrefStoreObserver : public PrefStore::Observer {
 public:
  MOCK_METHOD(void, OnInitializationCompleted, (bool), (override));
};

class MockReadErrorDelegate : public PersistentPrefStore::ReadErrorDelegate {
 public:
  MOCK_METHOD(void, OnError, (PersistentPrefStore::PrefReadError), (override));
};

enum class CommitPendingWriteMode {
  // Basic mode.
  WITHOUT_CALLBACK,
  // With reply callback.
  WITH_CALLBACK,
  // With synchronous notify callback (synchronous after the write -- shouldn't
  // require pumping messages to observe).
  WITH_SYNCHRONOUS_CALLBACK,
};

base::test::TaskEnvironment::ThreadPoolExecutionMode GetExecutionMode(
    CommitPendingWriteMode commit_mode) {
  switch (commit_mode) {
    case CommitPendingWriteMode::WITHOUT_CALLBACK:
      [[fallthrough]];
    case CommitPendingWriteMode::WITH_CALLBACK:
      return base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED;
    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK:
      // Synchronous callbacks require async tasks to run on their own.
      return base::test::TaskEnvironment::ThreadPoolExecutionMode::ASYNC;
  }
}

void CommitPendingWrite(JsonPrefStore* pref_store,
                        CommitPendingWriteMode commit_pending_write_mode,
                        base::test::TaskEnvironment* task_environment) {
  switch (commit_pending_write_mode) {
    case CommitPendingWriteMode::WITHOUT_CALLBACK: {
      pref_store->CommitPendingWrite();
      task_environment->RunUntilIdle();
      break;
    }
    case CommitPendingWriteMode::WITH_CALLBACK: {
      TestCommitPendingWriteWithCallback(pref_store, task_environment);
      break;
    }
    case CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK: {
      base::WaitableEvent written;
      pref_store->CommitPendingWrite(
          base::OnceClosure(),
          base::BindOnce(&base::WaitableEvent::Signal, Unretained(&written)));
      written.Wait();
      break;
    }
  }
}

class JsonPrefStoreTest
    : public testing::TestWithParam<CommitPendingWriteMode> {
 public:
  JsonPrefStoreTest()
      : task_environment_(base::test::TaskEnvironment::MainThreadType::DEFAULT,
                          GetExecutionMode(GetParam())) {}

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

 protected:
  void SetUp() override {
    commit_pending_write_mode_ = GetParam();

    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  }

  // The path to temporary directory used to contain the test operations.
  base::ScopedTempDir temp_dir_;

  base::test::TaskEnvironment task_environment_;
  CommitPendingWriteMode commit_pending_write_mode_;
};

}  // namespace

// Test fallback behavior for a nonexistent file.
TEST_P(JsonPrefStoreTest, NonExistentFile) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_NO_FILE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());
  EXPECT_EQ(0u, pref_store->get_writer().previous_data_size());
}

// Test fallback behavior for an invalid file.
TEST_P(JsonPrefStoreTest, InvalidFile) {
  base::FilePath invalid_file = temp_dir_.GetPath().AppendASCII("invalid.json");
  ASSERT_TRUE(base::WriteFile(invalid_file, kInvalidJson));

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(invalid_file);
  EXPECT_EQ(PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());

  // The file should have been moved aside.
  EXPECT_FALSE(PathExists(invalid_file));
  base::FilePath moved_aside = temp_dir_.GetPath().AppendASCII("invalid.bad");
  EXPECT_TRUE(PathExists(moved_aside));

  std::string moved_aside_contents;
  ASSERT_TRUE(base::ReadFileToString(moved_aside, &moved_aside_contents));
  EXPECT_EQ(kInvalidJson, moved_aside_contents);
}

// This function is used to avoid code duplication while testing synchronous
// and asynchronous version of the JsonPrefStore loading. It validates that the
// given output file's contents matches kWriteGolden.
void RunBasicJsonPrefStoreTest(JsonPrefStore* pref_store,
                               const base::FilePath& output_file,
                               CommitPendingWriteMode commit_pending_write_mode,
                               base::test::TaskEnvironment* task_environment) {
  const char kNewWindowsInTabs[] = "tabs.new_windows_in_tabs";
  const char kMaxTabs[] = "tabs.max_tabs";
  const char kLongIntPref[] = "long_int.pref";

  std::string cnn("http://www.cnn.com");

  const Value* actual;
  EXPECT_TRUE(pref_store->GetValue(kHomePage, &actual));
  EXPECT_TRUE(actual->is_string());
  EXPECT_EQ(cnn, actual->GetString());

  const char kSomeDirectory[] = "some_directory";

  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
  EXPECT_TRUE(actual->is_string());
  EXPECT_EQ("/usr/local/", actual->GetString());
  base::FilePath some_path(FILE_PATH_LITERAL("/usr/sbin/"));

  pref_store->SetValue(kSomeDirectory, Value(some_path.AsUTF8Unsafe()),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kSomeDirectory, &actual));
  EXPECT_TRUE(actual->is_string());
  EXPECT_EQ(some_path.AsUTF8Unsafe(), actual->GetString());

  // Test reading some other data types from sub-dictionaries.
  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
  EXPECT_TRUE(actual->is_bool());
  EXPECT_TRUE(actual->GetBool());

  pref_store->SetValue(kNewWindowsInTabs, Value(false),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kNewWindowsInTabs, &actual));
  EXPECT_TRUE(actual->is_bool());
  EXPECT_FALSE(actual->GetBool());

  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
  ASSERT_TRUE(actual->is_int());
  EXPECT_EQ(20, actual->GetInt());
  pref_store->SetValue(kMaxTabs, Value(10),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kMaxTabs, &actual));
  ASSERT_TRUE(actual->is_int());
  EXPECT_EQ(10, actual->GetInt());

  pref_store->SetValue(kLongIntPref,
                       Value(base::NumberToString(214748364842LL)),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  EXPECT_TRUE(pref_store->GetValue(kLongIntPref, &actual));
  EXPECT_TRUE(actual->is_string());
  int64_t value;
  base::StringToInt64(actual->GetString(), &value);
  EXPECT_EQ(214748364842LL, value);

  // Serialize and compare to expected output.
  CommitPendingWrite(pref_store, commit_pending_write_mode, task_environment);

  std::string output_contents;
  ASSERT_TRUE(base::ReadFileToString(output_file, &output_contents));
  EXPECT_EQ(kWriteGolden, output_contents);
  ASSERT_TRUE(base::DeleteFile(output_file));
}

TEST_P(JsonPrefStoreTest, Basic) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_TRUE(base::WriteFile(input_file, kReadJson));

  // Test that the persistent value can be loaded.
  ASSERT_TRUE(PathExists(input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);
  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());
  EXPECT_TRUE(pref_store->IsInitializationComplete());
  EXPECT_GT(pref_store->get_writer().previous_data_size(), 0u);

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file,
                            commit_pending_write_mode_, &task_environment_);
}

TEST_P(JsonPrefStoreTest, BasicAsync) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_TRUE(base::WriteFile(input_file, kReadJson));

  // Test that the persistent value can be loaded.
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(input_file);

  {
    MockPrefStoreObserver mock_observer;
    pref_store->AddObserver(&mock_observer);

    MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;
    pref_store->ReadPrefsAsync(mock_error_delegate);

    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
    EXPECT_CALL(*mock_error_delegate,
                OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
    task_environment_.RunUntilIdle();
    pref_store->RemoveObserver(&mock_observer);

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_TRUE(pref_store->IsInitializationComplete());
    EXPECT_GT(pref_store->get_writer().previous_data_size(), 0u);
  }

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file,
                            commit_pending_write_mode_, &task_environment_);
}

TEST_P(JsonPrefStoreTest, PreserveEmptyValues) {
  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);

  // Set some keys with empty values.
  pref_store->SetValue("list", base::Value(base::Value::List()),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  pref_store->SetValue("dict", base::Value(base::Value::Dict()),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  // Write to file.
  CommitPendingWrite(pref_store.get(), commit_pending_write_mode_,
                     &task_environment_);

  // Reload.
  pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);
  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_NONE, pref_store->ReadPrefs());
  ASSERT_FALSE(pref_store->ReadOnly());

  // Check values.
  const Value* result = nullptr;
  EXPECT_TRUE(pref_store->GetValue("list", &result));
  EXPECT_EQ(Value::List(), *result);
  EXPECT_TRUE(pref_store->GetValue("dict", &result));
  EXPECT_EQ(Value::Dict(), *result);
}

// This test is just documenting some potentially non-obvious behavior. It
// shouldn't be taken as normative.
TEST_P(JsonPrefStoreTest, RemoveClearsEmptyParent) {
  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty_values.json");

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);

  base::Value::Dict dict;
  dict.Set("key", "value");
  pref_store->SetValue("dict", base::Value(std::move(dict)),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  pref_store->RemoveValue("dict.key",
                          WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  const base::Value* retrieved_dict = nullptr;
  bool has_dict = pref_store->GetValue("dict", &retrieved_dict);
  EXPECT_FALSE(has_dict);
}

// Tests asynchronous reading of the file when there is no file.
TEST_P(JsonPrefStoreTest, AsyncNonExistingFile) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);
  MockPrefStoreObserver mock_observer;
  pref_store->AddObserver(&mock_observer);

  MockReadErrorDelegate *mock_error_delegate = new MockReadErrorDelegate;
  pref_store->ReadPrefsAsync(mock_error_delegate);

  EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
  EXPECT_CALL(*mock_error_delegate,
              OnError(PersistentPrefStore::PREF_READ_ERROR_NO_FILE)).Times(1);
  task_environment_.RunUntilIdle();
  pref_store->RemoveObserver(&mock_observer);

  EXPECT_FALSE(pref_store->ReadOnly());
}

TEST_P(JsonPrefStoreTest, ReadWithInterceptor) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_TRUE(base::WriteFile(input_file, kReadJson));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter());
  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
      intercepting_pref_filter.get();
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, std::move(intercepting_pref_filter));

  ASSERT_EQ(PersistentPrefStore::PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE,
            pref_store->ReadPrefs());
  EXPECT_FALSE(pref_store->ReadOnly());

  // The store shouldn't be considered initialized until the interceptor
  // returns.
  EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
  EXPECT_FALSE(pref_store->IsInitializationComplete());
  EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));

  raw_intercepting_pref_filter_->ReleasePrefs();

  EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
  EXPECT_TRUE(pref_store->IsInitializationComplete());
  EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file,
                            commit_pending_write_mode_, &task_environment_);
}

TEST_P(JsonPrefStoreTest, ReadAsyncWithInterceptor) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_TRUE(base::WriteFile(input_file, kReadJson));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter());
  InterceptingPrefFilter* raw_intercepting_pref_filter_ =
      intercepting_pref_filter.get();
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, std::move(intercepting_pref_filter));

  MockPrefStoreObserver mock_observer;
  pref_store->AddObserver(&mock_observer);

  // Ownership of the |mock_error_delegate| is handed to the |pref_store| below.
  MockReadErrorDelegate* mock_error_delegate = new MockReadErrorDelegate;

  {
    pref_store->ReadPrefsAsync(mock_error_delegate);

    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(0);
    // EXPECT_CALL(*mock_error_delegate,
    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);
    task_environment_.RunUntilIdle();

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_TRUE(raw_intercepting_pref_filter_->has_intercepted_prefs());
    EXPECT_FALSE(pref_store->IsInitializationComplete());
    EXPECT_FALSE(pref_store->GetValue(kHomePage, nullptr));
  }

  {
    EXPECT_CALL(mock_observer, OnInitializationCompleted(true)).Times(1);
    // EXPECT_CALL(*mock_error_delegate,
    //             OnError(PersistentPrefStore::PREF_READ_ERROR_NONE)).Times(0);

    raw_intercepting_pref_filter_->ReleasePrefs();

    EXPECT_FALSE(pref_store->ReadOnly());
    EXPECT_FALSE(raw_intercepting_pref_filter_->has_intercepted_prefs());
    EXPECT_TRUE(pref_store->IsInitializationComplete());
    EXPECT_TRUE(pref_store->GetValue(kHomePage, nullptr));
  }

  pref_store->RemoveObserver(&mock_observer);

  // The JSON file looks like this:
  // {
  //   "homepage": "http://www.cnn.com",
  //   "some_directory": "/usr/local/",
  //   "tabs": {
  //     "new_windows_in_tabs": true,
  //     "max_tabs": 20
  //   }
  // }

  RunBasicJsonPrefStoreTest(pref_store.get(), input_file,
                            commit_pending_write_mode_, &task_environment_);
}

TEST_P(JsonPrefStoreTest, RemoveValuesByPrefix) {
  FilePath pref_file = temp_dir_.GetPath().AppendASCII("empty.json");

  auto pref_store = base::MakeRefCounted<JsonPrefStore>(pref_file);

  const Value* value;
  const std::string prefix = "pref";
  const std::string subpref_name1 = "pref.a";
  const std::string subpref_name2 = "pref.b";
  const std::string other_name = "other";

  pref_store->SetValue(subpref_name1, base::Value(42),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  pref_store->SetValue(subpref_name2, base::Value(42),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  pref_store->SetValue(other_name, base::Value(42),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);

  pref_store->RemoveValuesByPrefixSilently(prefix);
  EXPECT_FALSE(pref_store->GetValue(subpref_name1, &value));
  EXPECT_FALSE(pref_store->GetValue(subpref_name2, &value));
  EXPECT_TRUE(pref_store->GetValue(other_name, &value));
}

TEST_P(JsonPrefStoreTest, HasReadErrorDelegate) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);

  EXPECT_FALSE(pref_store->HasReadErrorDelegate());

  pref_store->ReadPrefsAsync(new MockReadErrorDelegate);
  EXPECT_TRUE(pref_store->HasReadErrorDelegate());
}

TEST_P(JsonPrefStoreTest, HasReadErrorDelegateWithNullDelegate) {
  base::FilePath bogus_input_file = temp_dir_.GetPath().AppendASCII("read.txt");
  ASSERT_FALSE(PathExists(bogus_input_file));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(bogus_input_file);

  EXPECT_FALSE(pref_store->HasReadErrorDelegate());

  pref_store->ReadPrefsAsync(nullptr);
  // Returns true even though no instance was passed.
  EXPECT_TRUE(pref_store->HasReadErrorDelegate());
}

INSTANTIATE_TEST_SUITE_P(
    JsonPrefStoreTestVariations,
    JsonPrefStoreTest,
    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK,
                      CommitPendingWriteMode::WITH_CALLBACK,
                      CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));

class JsonPrefStoreLossyWriteTest : public JsonPrefStoreTest {
 public:
  JsonPrefStoreLossyWriteTest() = default;

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

 protected:
  void SetUp() override {
    JsonPrefStoreTest::SetUp();
    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
  }

  scoped_refptr<JsonPrefStore> CreatePrefStore() {
    return base::MakeRefCounted<JsonPrefStore>(test_file_);
  }

  // Return the ImportantFileWriter for a given JsonPrefStore.
  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
    return &(pref_store->writer_);
  }

  // Get the contents of kTestFile. Pumps the message loop before returning the
  // result.
  std::string GetTestFileContents() {
    task_environment_.RunUntilIdle();
    std::string file_contents;
    ReadFileToString(test_file_, &file_contents);
    return file_contents;
  }

 private:
  base::FilePath test_file_;
};

TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteBasic) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a normal pref and check that it gets scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("normal", base::Value("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"normal\":\"normal\"}", GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Set a lossy pref and check that it is not scheduled to be written.
  // SetValue/RemoveValue.
  pref_store->SetValue("lossy", base::Value("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->RemoveValue("lossy", WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // SetValueSilently/RemoveValueSilently.
  pref_store->SetValueSilently("lossy", base::Value("lossy"),
                               WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->RemoveValueSilently("lossy",
                                  WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // ReportValueChanged.
  pref_store->SetValue("lossy", base::Value("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->ReportValueChanged("lossy",
                                 WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Call CommitPendingWrite and check that the lossy pref and the normal pref
  // are there with the last values set above.
  pref_store->CommitPendingWrite(base::OnceClosure());
  ASSERT_FALSE(file_writer->HasPendingWrite());
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
}

TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossyFirst) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a lossy pref and check that it is not scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("lossy", base::Value("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Set a normal pref and check that it is scheduled to be written.
  pref_store->SetValue("normal", base::Value("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call DoScheduledWrite and check both prefs get written.
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());
}

TEST_P(JsonPrefStoreLossyWriteTest, LossyWriteMixedLossySecond) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a normal pref and check that it is scheduled to be written.
  ASSERT_FALSE(file_writer->HasPendingWrite());
  pref_store->SetValue("normal", base::Value("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Set a lossy pref and check that the write is still scheduled.
  pref_store->SetValue("lossy", base::Value("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call DoScheduledWrite and check both prefs get written.
  file_writer->DoScheduledWrite();
  ASSERT_EQ("{\"lossy\":\"lossy\",\"normal\":\"normal\"}",
            GetTestFileContents());
  ASSERT_FALSE(file_writer->HasPendingWrite());
}

TEST_P(JsonPrefStoreLossyWriteTest, ScheduleLossyWrite) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Set a lossy pref and check that it is not scheduled to be written.
  pref_store->SetValue("lossy", base::Value("lossy"),
                       WriteablePrefStore::LOSSY_PREF_WRITE_FLAG);
  ASSERT_FALSE(file_writer->HasPendingWrite());

  // Schedule pending lossy writes and check that it is scheduled.
  pref_store->SchedulePendingLossyWrites();
  ASSERT_TRUE(file_writer->HasPendingWrite());

  // Call CommitPendingWrite and check that the lossy pref is there with the
  // last value set above.
  pref_store->CommitPendingWrite(base::OnceClosure());
  ASSERT_FALSE(file_writer->HasPendingWrite());
  ASSERT_EQ("{\"lossy\":\"lossy\"}", GetTestFileContents());
}

INSTANTIATE_TEST_SUITE_P(
    JsonPrefStoreLossyWriteTestVariations,
    JsonPrefStoreLossyWriteTest,
    ::testing::Values(CommitPendingWriteMode::WITHOUT_CALLBACK,
                      CommitPendingWriteMode::WITH_CALLBACK,
                      CommitPendingWriteMode::WITH_SYNCHRONOUS_CALLBACK));

class SuccessfulWriteReplyObserver {
 public:
  SuccessfulWriteReplyObserver() = default;

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

  // Returns true if a successful write was observed via on_successful_write()
  // and resets the observation state to false regardless.
  bool GetAndResetObservationState() {
    bool was_successful_write_observed = successful_write_reply_observed_;
    successful_write_reply_observed_ = false;
    return was_successful_write_observed;
  }

  // Register OnWrite() to be called on the next write of |json_pref_store|.
  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);

  void OnSuccessfulWrite() {
    EXPECT_FALSE(successful_write_reply_observed_);
    successful_write_reply_observed_ = true;
  }

 private:
  bool successful_write_reply_observed_ = false;
};

void SuccessfulWriteReplyObserver::ObserveNextWriteCallback(
    JsonPrefStore* json_pref_store) {
  json_pref_store->RegisterOnNextSuccessfulWriteReply(
      base::BindOnce(&SuccessfulWriteReplyObserver::OnSuccessfulWrite,
                     base::Unretained(this)));
}

enum WriteCallbackObservationState {
  NOT_CALLED,
  CALLED_WITH_ERROR,
  CALLED_WITH_SUCCESS,
};

class WriteCallbacksObserver {
 public:
  WriteCallbacksObserver() = default;

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

  // Register OnWrite() to be called on the next write of |json_pref_store|.
  void ObserveNextWriteCallback(JsonPrefStore* json_pref_store);

  // Returns whether OnPreWrite() was called, and resets the observation state
  // to false.
  bool GetAndResetPreWriteObservationState();

  // Returns the |WriteCallbackObservationState| which was observed, then resets
  // it to |NOT_CALLED|.
  WriteCallbackObservationState GetAndResetPostWriteObservationState();

  JsonPrefStore::OnWriteCallbackPair GetCallbackPair() {
    return std::make_pair(base::BindOnce(&WriteCallbacksObserver::OnPreWrite,
                                         base::Unretained(this)),
                          base::BindOnce(&WriteCallbacksObserver::OnPostWrite,
                                         base::Unretained(this)));
  }

  void OnPreWrite() {
    EXPECT_FALSE(pre_write_called_);
    pre_write_called_ = true;
  }

  void OnPostWrite(bool success) {
    EXPECT_EQ(NOT_CALLED, post_write_observation_state_);
    post_write_observation_state_ =
        success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
  }

 private:
  bool pre_write_called_ = false;
  WriteCallbackObservationState post_write_observation_state_ = NOT_CALLED;
};

void WriteCallbacksObserver::ObserveNextWriteCallback(JsonPrefStore* writer) {
  writer->RegisterOnNextWriteSynchronousCallbacks(GetCallbackPair());
}

bool WriteCallbacksObserver::GetAndResetPreWriteObservationState() {
  bool observation_state = pre_write_called_;
  pre_write_called_ = false;
  return observation_state;
}

WriteCallbackObservationState
WriteCallbacksObserver::GetAndResetPostWriteObservationState() {
  WriteCallbackObservationState state = post_write_observation_state_;
  pre_write_called_ = false;
  post_write_observation_state_ = NOT_CALLED;
  return state;
}

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

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

 protected:
  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    test_file_ = temp_dir_.GetPath().AppendASCII("test.json");
  }

  scoped_refptr<JsonPrefStore> CreatePrefStore() {
    return base::MakeRefCounted<JsonPrefStore>(test_file_);
  }

  // Return the ImportantFileWriter for a given JsonPrefStore.
  ImportantFileWriter* GetImportantFileWriter(JsonPrefStore* pref_store) {
    return &(pref_store->writer_);
  }

  void TriggerFakeWriteForCallback(JsonPrefStore* pref_store, bool success) {
    JsonPrefStore::PostWriteCallback(
        base::BindOnce(&JsonPrefStore::RunOrScheduleNextSuccessfulWriteCallback,
                       pref_store->AsWeakPtr()),
        base::BindOnce(&WriteCallbacksObserver::OnPostWrite,
                       base::Unretained(&write_callback_observer_)),
        base::SequencedTaskRunner::GetCurrentDefault(), success);
  }

  SuccessfulWriteReplyObserver successful_write_reply_observer_;
  WriteCallbacksObserver write_callback_observer_;

 protected:
  base::test::TaskEnvironment task_environment_{
      base::test::TaskEnvironment::MainThreadType::DEFAULT,
      base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED};

  base::ScopedTempDir temp_dir_;

 private:
  base::FilePath test_file_;
};

TEST_F(JsonPrefStoreCallbackTest, TestSerializeDataCallbacks) {
  base::FilePath input_file = temp_dir_.GetPath().AppendASCII("write.json");
  ASSERT_TRUE(base::WriteFile(input_file, kReadJson));

  std::unique_ptr<InterceptingPrefFilter> intercepting_pref_filter(
      new InterceptingPrefFilter(write_callback_observer_.GetCallbackPair()));
  auto pref_store = base::MakeRefCounted<JsonPrefStore>(
      input_file, std::move(intercepting_pref_filter));
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());
  pref_store->SetValue("normal", base::Value("normal"),
                       WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
  file_writer->DoScheduledWrite();

  // The observer should not be invoked right away.
  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  task_environment_.RunUntilIdle();

  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacks) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());

  // Test RegisterOnNextWriteSynchronousCallbacks after
  // RegisterOnNextSuccessfulWriteReply.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow("foo");
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextSuccessfulWriteReply after
  // RegisterOnNextWriteSynchronousCallbacks.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow("foo");
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextSuccessfulWriteReply only.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow("foo");
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_FALSE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Test RegisterOnNextWriteSynchronousCallbacks only.
  write_callback_observer_.ObserveNextWriteCallback(pref_store.get());
  file_writer->WriteNow("foo");
  task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksWithFakeFailure) {
  scoped_refptr<JsonPrefStore> pref_store = CreatePrefStore();

  // Confirm that the observers are invoked.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), true);
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that the observation states were reset.
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that re-installing the observers works for another write.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), true);
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Confirm that the successful observer is not invoked by an unsuccessful
  // write, and that the synchronous observer is invoked.
  successful_write_reply_observer_.ObserveNextWriteCallback(pref_store.get());
  TriggerFakeWriteForCallback(pref_store.get(), false);
  task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(CALLED_WITH_ERROR,
            write_callback_observer_.GetAndResetPostWriteObservationState());

  // Do a real write, and confirm that the successful observer was invoked after
  // being set by |PostWriteCallback| by the last TriggerFakeWriteCallback.
  ImportantFileWriter* file_writer = GetImportantFileWriter(pref_store.get());
  file_writer->WriteNow("foo");
  task_environment_.RunUntilIdle();
  EXPECT_TRUE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_EQ(NOT_CALLED,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

TEST_F(JsonPrefStoreCallbackTest, TestPostWriteCallbacksDuringProfileDeath) {
  // Create a JsonPrefStore and attach observers to it, then delete it by making
  // it go out of scope to simulate profile switch or Chrome shutdown.
  {
    scoped_refptr<JsonPrefStore> soon_out_of_scope_pref_store =
        CreatePrefStore();
    ImportantFileWriter* file_writer =
        GetImportantFileWriter(soon_out_of_scope_pref_store.get());
    successful_write_reply_observer_.ObserveNextWriteCallback(
        soon_out_of_scope_pref_store.get());
    write_callback_observer_.ObserveNextWriteCallback(
        soon_out_of_scope_pref_store.get());
    file_writer->WriteNow("foo");
  }
  task_environment_.RunUntilIdle();
  EXPECT_FALSE(successful_write_reply_observer_.GetAndResetObservationState());
  EXPECT_TRUE(write_callback_observer_.GetAndResetPreWriteObservationState());
  EXPECT_EQ(CALLED_WITH_SUCCESS,
            write_callback_observer_.GetAndResetPostWriteObservationState());
}

}  // namespace base