910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "services/preferences/tracked/pref_hash_store_impl.h"

#include <string>

#include "base/base64.h"
#include "base/values.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "services/preferences/public/mojom/tracked_preference_validation_delegate.mojom.h"
#include "services/preferences/tracked/dictionary_hash_store_contents.h"
#include "services/preferences/tracked/hash_store_contents.h"
#include "services/preferences/tracked/pref_hash_calculator.h"
#include "services/preferences/tracked/pref_hash_store_transaction.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include "base/enterprise_util.h"
#endif

namespace {
using ValueState =
    prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;

// Helper to get derived key for encrypted hashes; this is a replicate of the
// function in the .cc file.
std::string GetEncKey(const std::string& path) {
  return path + "_encrypted_hash";
}

// Helper to get derived key for encrypted split hashes; calling the GetEncKey
// directly for testing.
std::string GetSplitEncKeyBase(const std::string& path) {
  return GetEncKey(path);
}
// Keys expected in the dictionary passed to ImportHash if it contains
// structured data.
const char kImportMacKey[] = "mac";
const char kImportEncryptedHashKey[] = "encrypted_hash";
}  // namespace

class PrefHashStoreImplTest : public testing::Test {
 public:
  PrefHashStoreImplTest() : contents_(pref_store_contents_) {}

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

 protected:
  HashStoreContents* GetHashStoreContents() { return &contents_; }

 private:
  base::Value::Dict pref_store_contents_;
  // Must be declared after |pref_store_contents_| as it needs to be outlived
  // by it.
  DictionaryHashStoreContents contents_;
};

TEST_F(PrefHashStoreImplTest, ComputeMac) {
  base::Value string_1("string1");
  base::Value string_2("string2");
  PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);

  std::string computed_mac_1 = pref_hash_store.ComputeMac("path1", &string_1);
  std::string computed_mac_2 = pref_hash_store.ComputeMac("path1", &string_2);
  std::string computed_mac_3 = pref_hash_store.ComputeMac("path2", &string_1);

  // Quick sanity checks here, see pref_hash_calculator_unittest.cc for more
  // complete tests.
  EXPECT_EQ(computed_mac_1, pref_hash_store.ComputeMac("path1", &string_1));
  EXPECT_NE(computed_mac_1, computed_mac_2);
  EXPECT_NE(computed_mac_1, computed_mac_3);
  EXPECT_EQ(64U, computed_mac_1.size());
}

TEST_F(PrefHashStoreImplTest, ComputeSplitMacs) {
  base::Value::Dict dict;
  dict.Set("a", "string1");
  dict.Set("b", "string2");
  // Verify that dictionary keys can contain a '.' delimiter.
  dict.Set("http://www.example.com", "string3");
  PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);

  base::Value::Dict computed_macs =
      pref_hash_store.ComputeSplitMacs("foo.bar", &dict);

  const std::string mac_1 = computed_macs.Find("a")->GetString();
  const std::string mac_2 = computed_macs.Find("b")->GetString();
  const std::string mac_3 =
      computed_macs.Find("http://www.example.com")->GetString();

  EXPECT_EQ(3U, computed_macs.size());

  base::Value string_1("string1");
  base::Value string_2("string2");
  base::Value string_3("string3");
  EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.a", &string_1), mac_1);
  EXPECT_EQ(pref_hash_store.ComputeMac("foo.bar.b", &string_2), mac_2);
  EXPECT_EQ(
      pref_hash_store.ComputeMac("foo.bar.http://www.example.com", &string_3),
      mac_3);
}

TEST_F(PrefHashStoreImplTest, ComputeNullSplitMacs) {
  PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
  base::Value::Dict computed_macs =
      pref_hash_store.ComputeSplitMacs("foo.bar", nullptr);

  EXPECT_TRUE(computed_macs.empty());
}

TEST_F(PrefHashStoreImplTest, AtomicHashStoreAndCheck) {
  base::Value string_1("string1");
  base::Value string_2("string2");

  {
    // 32 NULL bytes is the seed that was used to generate the legacy hash.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    // Only NULL should be trusted in the absence of a hash.
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              transaction->CheckValue("path1", NULL));

    transaction->StoreHash("path1", &string_1);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::CLEARED, transaction->CheckValue("path1", NULL));
    transaction->StoreHash("path1", NULL);
    EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", NULL));
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));

    base::Value dict_val(base::Value::Type::DICT);
    base::Value::Dict& dict = dict_val.GetDict();
    dict.Set("a", "foo");
    dict.Set("d", "bad");
    dict.Set("b", "bar");
    dict.Set("c", "baz");

    transaction->StoreHash("path1", &dict_val);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &dict_val));
  }

  ASSERT_FALSE(GetHashStoreContents()->GetSuperMac().empty());

  {
    // |pref_hash_store| should trust its initial hashes dictionary and thus
    // trust new unknown values.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_1));
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_2));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              transaction->CheckValue("new_path", NULL));
  }

  // Manually corrupt the super MAC.
  GetHashStoreContents()->SetSuperMac(std::string(64, 'A'));

  {
    // |pref_hash_store| should no longer trust its initial hashes dictionary
    // and thus shouldn't trust non-NULL unknown values.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_1));
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_2));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              transaction->CheckValue("new_path", NULL));
  }
}

TEST_F(PrefHashStoreImplTest, ImportExportOperations) {
  base::Value string_1("string1");
  base::Value string_2("string2");

  // Initial state: no super MAC.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_FALSE(transaction->IsSuperMACValid());

    ASSERT_FALSE(transaction->HasHash("path1"));

    // Storing a hash will stamp the super MAC.
    transaction->StoreHash("path1", &string_1);

    ASSERT_TRUE(transaction->HasHash("path1"));
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
  }

  // Make a copy of the stored hash for future use.
  const base::Value* hash =
      GetHashStoreContents()->GetContents()->Find("path1");
  ASSERT_TRUE(hash);
  base::Value path_1_string_1_hash_copy(hash->Clone());
  hash = nullptr;

  // Verify that the super MAC was stamped.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_TRUE(transaction->IsSuperMACValid());
    ASSERT_TRUE(transaction->HasHash("path1"));

    // Clearing the hash should preserve validity.
    transaction->ClearHash("path1");

    // The effects of the clear should be immediately visible.
    ASSERT_FALSE(transaction->HasHash("path1"));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              transaction->CheckValue("path1", NULL));
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("path1", &string_1));
  }

  // Verify that validity was preserved and that the clear took effect.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_TRUE(transaction->IsSuperMACValid());
    ASSERT_FALSE(transaction->HasHash("path1"));
  }

  // Invalidate the super MAC.
  GetHashStoreContents()->SetSuperMac(std::string());

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_FALSE(transaction->IsSuperMACValid());
    ASSERT_FALSE(transaction->HasHash("path1"));

    // An import should preserve invalidity.
    transaction->ImportHash("path1", &path_1_string_1_hash_copy);

    ASSERT_TRUE(transaction->HasHash("path1"));

    // The imported hash should be usable for validating the original value.
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
  }

  // Verify that invalidity was preserved and that the import took effect.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_FALSE(transaction->IsSuperMACValid());
    ASSERT_TRUE(transaction->HasHash("path1"));
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));

    // After clearing the hash, non-null values are UNTRUSTED_UNKNOWN.
    transaction->ClearHash("path1");

    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              transaction->CheckValue("path1", NULL));
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("path1", &string_1));
  }

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_FALSE(transaction->IsSuperMACValid());

    // Test StampSuperMac.
    transaction->StampSuperMac();
  }

  // Verify that the store is now valid.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_TRUE(transaction->IsSuperMACValid());

    // Store the hash of a different value to test an "over-import".
    transaction->StoreHash("path1", &string_2);
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_2));
  }

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_TRUE(transaction->IsSuperMACValid());

    // "Over-import". An import should preserve validity.
    transaction->ImportHash("path1", &path_1_string_1_hash_copy);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
  }

  // Verify that validity was preserved and the "over-import" took effect.
  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    ASSERT_TRUE(transaction->IsSuperMACValid());
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_1));
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckValue("path1", &string_2));
  }
}

TEST_F(PrefHashStoreImplTest, SuperMACDisabled) {
  base::Value string_1("string1");
  base::Value string_2("string2");

  {
    // Pass |use_super_mac| => false.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), false);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    transaction->StoreHash("path1", &string_2);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckValue("path1", &string_2));
  }

  ASSERT_TRUE(GetHashStoreContents()->GetSuperMac().empty());

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), false);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckValue("new_path", &string_1));
  }
}

TEST_F(PrefHashStoreImplTest, SplitHashStoreAndCheck) {
  base::Value::Dict dict;
  dict.Set("a", base::Value("to be replaced"));
  dict.Set("unchanged.path.with.dots", base::Value("same"));
  dict.Set("o", base::Value("old"));

  base::Value::Dict modified_dict;
  modified_dict.Set("a", base::Value("replaced"));
  modified_dict.Set("unchanged.path.with.dots", base::Value("same"));
  modified_dict.Set("c", base::Value("new"));

  base::Value::Dict empty_dict;

  std::vector<std::string> invalid_keys;

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    // No hashes stored yet and hashes dictionary is empty (and thus not
    // trusted).
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    transaction->StoreSplitHash("path1", &dict);

    // Verify match post storage.
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify new path is still unknown.
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path2", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify NULL or empty dicts are declared as having been cleared.
    EXPECT_EQ(ValueState::CLEARED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));

    // invalid_keys should contain the keys that were removed.
    std::vector<std::string> expected_cleared_keys;
    expected_cleared_keys.push_back("a");
    expected_cleared_keys.push_back("unchanged.path.with.dots");
    expected_cleared_keys.push_back("o");

    // Sort both vectors for a stable comparison.
    std::sort(expected_cleared_keys.begin(), expected_cleared_keys.end());
    std::sort(invalid_keys.begin(), invalid_keys.end());
    EXPECT_EQ(expected_cleared_keys, invalid_keys);
    invalid_keys.clear();

    EXPECT_EQ(ValueState::CLEARED, transaction->CheckSplitValue(
                                       "path1", &empty_dict, &invalid_keys));
    // The same keys should be reported as invalid/cleared for an empty dict.
    std::sort(invalid_keys.begin(), invalid_keys.end());
    EXPECT_EQ(expected_cleared_keys, invalid_keys);
    invalid_keys.clear();

    // Verify changes are properly detected.
    EXPECT_EQ(ValueState::CHANGED, transaction->CheckSplitValue(
                                       "path1", &modified_dict, &invalid_keys));
    std::vector<std::string> expected_invalid_keys1;
    expected_invalid_keys1.push_back("a");
    expected_invalid_keys1.push_back("c");
    expected_invalid_keys1.push_back("o");
    EXPECT_EQ(expected_invalid_keys1, invalid_keys);
    invalid_keys.clear();

    // Verify |dict| still matches post check.
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Store hash for |modified_dict|.
    transaction->StoreSplitHash("path1", &modified_dict);

    // Verify |modified_dict| is now the one that verifies correctly.
    EXPECT_EQ(
        ValueState::UNCHANGED,
        transaction->CheckSplitValue("path1", &modified_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Verify old dict no longer matches.
    EXPECT_EQ(ValueState::CHANGED,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    std::vector<std::string> expected_invalid_keys2;
    expected_invalid_keys2.push_back("a");
    expected_invalid_keys2.push_back("o");
    expected_invalid_keys2.push_back("c");
    EXPECT_EQ(expected_invalid_keys2, invalid_keys);
    invalid_keys.clear();
  }

  {
    // |pref_hash_store| should trust its initial hashes dictionary and thus
    // trust new unknown values.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
  {
    // Check the same as above for a path with dots.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(
        ValueState::TRUSTED_UNKNOWN_VALUE,
        transaction->CheckSplitValue("path.with.dots", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }

  // Manually corrupt the super MAC.
  GetHashStoreContents()->SetSuperMac(std::string(64, 'A'));

  {
    // |pref_hash_store| should no longer trust its initial hashes dictionary
    // and thus shouldn't trust unknown values.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("new_path", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
  {
    // Check the same as above for a path with dots.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    EXPECT_EQ(
        ValueState::UNTRUSTED_UNKNOWN_VALUE,
        transaction->CheckSplitValue("path.with.dots", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

TEST_F(PrefHashStoreImplTest, EmptyAndNULLSplitDict) {
  base::Value::Dict empty_dict;

  std::vector<std::string> invalid_keys;

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    // Store hashes for a random dict to be overwritten below.
    base::Value::Dict initial_dict;
    initial_dict.Set("a", "foo");
    transaction->StoreSplitHash("path1", &initial_dict);

    // Verify stored empty dictionary matches NULL and empty dictionary back.
    transaction->StoreSplitHash("path1", &empty_dict);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
    EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue(
                                         "path1", &empty_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());

    // Same when storing NULL directly.
    transaction->StoreSplitHash("path1", NULL);
    EXPECT_EQ(ValueState::UNCHANGED,
              transaction->CheckSplitValue("path1", NULL, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
    EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckSplitValue(
                                         "path1", &empty_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }

  {
    // |pref_hash_store| should trust its initial hashes dictionary (and thus
    // trust new unknown values) even though the last action done was to clear
    // the hashes for path1 by setting its value to NULL (this is a regression
    // test ensuring that the internal action of clearing some hashes does
    // update the stored hash of hashes).
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    base::Value::Dict tested_dict;
    tested_dict.Set("a", "foo");
    tested_dict.Set("b", "bar");
    EXPECT_EQ(
        ValueState::TRUSTED_UNKNOWN_VALUE,
        transaction->CheckSplitValue("new_path", &tested_dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

// Test that the PrefHashStore returns TRUSTED_UNKNOWN_VALUE when checking for
// a split preference even if there is an existing atomic preference's hash
// stored. There is no point providing a migration path for preferences
// switching strategies after their initial release as split preferences are
// turned into split preferences specifically because the atomic hash isn't
// considered useful.
TEST_F(PrefHashStoreImplTest, TrustedUnknownSplitValueFromExistingAtomic) {
  base::Value string("string1");

  base::Value::Dict dict;
  dict.Set("a", "foo");
  dict.Set("d", "bad");
  dict.Set("b", "bar");
  dict.Set("c", "baz");

  {
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));

    transaction->StoreHash("path1", &string);
    EXPECT_EQ(ValueState::UNCHANGED, transaction->CheckValue("path1", &string));
  }

  {
    // Load a new |pref_hash_store| in which the hashes dictionary is trusted.
    PrefHashStoreImpl pref_hash_store(std::string(32, 0), true);
    std::unique_ptr<PrefHashStoreTransaction> transaction(
        pref_hash_store.BeginTransaction(GetHashStoreContents()));
    std::vector<std::string> invalid_keys;
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE,
              transaction->CheckSplitValue("path1", &dict, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

class PrefHashStoreImplEncryptedTest : public testing::Test {
 public:
  const std::string kSeed = "test_seed_store_encrypted";

  PrefHashStoreImplEncryptedTest()
      : hash_store_(kSeed, /*use_super_mac=*/true),
        test_encryptor_(os_crypt_async::GetTestEncryptorForTesting()),
        dictionary_contents_(pref_store_contents_) {}

 protected:
  std::unique_ptr<PrefHashStoreTransaction> BeginTransaction(
      bool with_encryptor) {
    const os_crypt_async::Encryptor* encryptor_arg =
        with_encryptor ? &test_encryptor_ : nullptr;
    return hash_store_.BeginTransaction(&dictionary_contents_, encryptor_arg);
  }

  void VerifyStoredHashes(
      const std::string& path,
      const std::optional<std::string>& expected_mac,
      const std::optional<std::string>& expected_encrypted_b64) {
    SCOPED_TRACE("Verifying hashes for path: " + path);
    std::string stored_mac;
    bool has_mac = dictionary_contents_.GetMac(path, &stored_mac);
    EXPECT_EQ(expected_mac.has_value(), has_mac);
    if (expected_mac) {
      EXPECT_EQ(*expected_mac, stored_mac);
    }
    std::string stored_enc;
    bool has_enc = dictionary_contents_.GetMac(GetEncKey(path), &stored_enc);
    EXPECT_EQ(expected_encrypted_b64.has_value(), has_enc);
    if (expected_encrypted_b64) {
      EXPECT_EQ(*expected_encrypted_b64, stored_enc);
    }
  }

  void VerifyStoredSplitHashes(
      const std::string& base_path,
      const std::string& key,  // sub-key within the split dict
      const std::optional<std::string>& expected_mac,
      const std::optional<std::string>& expected_encrypted_b64) {
    SCOPED_TRACE("Verifying split hashes for path: " + base_path + "." + key);
    std::map<std::string, std::string> split_macs;
    dictionary_contents_.GetSplitMacs(base_path, &split_macs);

    auto mac_it = split_macs.find(key);
    EXPECT_EQ(expected_mac.has_value(), mac_it != split_macs.end());
    if (expected_mac && mac_it != split_macs.end()) {
      EXPECT_EQ(*expected_mac, mac_it->second);
    } else if (expected_mac) {
      ADD_FAILURE() << "Expected MAC for " << key << " not found.";
    }

    std::map<std::string, std::string> split_enc_hashes;
    dictionary_contents_.GetSplitMacs(GetSplitEncKeyBase(base_path),
                                      &split_enc_hashes);
    auto enc_it = split_enc_hashes.find(key);
    EXPECT_EQ(expected_encrypted_b64.has_value(),
              enc_it != split_enc_hashes.end());
    if (expected_encrypted_b64 && enc_it != split_enc_hashes.end()) {
      EXPECT_EQ(*expected_encrypted_b64, enc_it->second);
    } else if (expected_encrypted_b64) {
      ADD_FAILURE() << "Expected Encrypted Hash for " << key << " not found.";
    }
  }

  void MakeSuperMACInvalid() { dictionary_contents_.SetSuperMac("invalid"); }
  void MakeSuperMACValid() {
    const base::Value::Dict* macs_dict = GetCurrentDictionaryContents();
    std::string valid_super_mac;
    if (macs_dict) {
      base::Value dict_value_wrapper(macs_dict->Clone());
      valid_super_mac = hash_store_.ComputeMac("", &dict_value_wrapper);
    } else {
      valid_super_mac =
          hash_store_.ComputeMac("", static_cast<const base::Value*>(nullptr));
    }
    dictionary_contents_.SetSuperMac(valid_super_mac);
  }

  void SeedAtomicMac(const std::string& path, const std::string& mac_value) {
    dictionary_contents_.SetMac(path, mac_value);
  }

  void SeedAtomicEncryptedHash(const std::string& path,
                               const std::string& eh_value_b64) {
    dictionary_contents_.SetMac(GetEncKey(path), eh_value_b64);
  }

  void SeedSplitMacs(const std::string& path,
                     const base::Value::Dict* dict_to_hash) {
    // Remove any existing entry at this path (atomic or old split dict)
    dictionary_contents_.RemoveEntry(path);
    if (dict_to_hash) {
      base::Value::Dict macs = hash_store_.ComputeSplitMacs(path, dict_to_hash);
      for (const auto item : macs) {
        if (item.second.is_string()) {
          dictionary_contents_.SetSplitMac(path, item.first,
                                           item.second.GetString());
        }
      }
    }
  }

  void SeedSplitEncryptedHashes(const std::string& path,
                                const base::Value::Dict* computed_hashes_dict) {
    std::string enc_base_key = GetSplitEncKeyBase(path);
    dictionary_contents_.RemoveEntry(
        enc_base_key);  // Remove potentially conflicting atomic entry
    dictionary_contents_.RemoveEntry(
        enc_base_key);  // Remove old split dict if present
    if (computed_hashes_dict) {
      for (auto item : *computed_hashes_dict) {
        if (item.second.is_string()) {
          dictionary_contents_.SetSplitMac(enc_base_key, item.first,
                                           item.second.GetString());
        }
      }
    }
  }

  // Seeds split encrypted hashes by computing them first.
  void SeedSplitEncryptedHashesFromValues(
      const std::string& path,
      const base::Value::Dict* values_to_hash) {
    std::string enc_base_key = GetSplitEncKeyBase(path);
    dictionary_contents_.RemoveEntry(enc_base_key);
    if (values_to_hash) {
      base::Value::Dict computed_hashes =
          hash_store_.ComputeSplitEncryptedHashes(path, values_to_hash,
                                                  &test_encryptor_);
      for (auto item : computed_hashes) {
        if (item.second.is_string()) {
          dictionary_contents_.SetSplitMac(enc_base_key, item.first,
                                           item.second.GetString());
        }
      }
    }
  }

  const base::Value::Dict* GetCurrentDictionaryContents() {
    return dictionary_contents_.GetContents();
  }

  PrefHashStoreImpl hash_store_;
  os_crypt_async::Encryptor test_encryptor_;
  base::Value::Dict pref_store_contents_;
  DictionaryHashStoreContents dictionary_contents_;
};

TEST_F(PrefHashStoreImplEncryptedTest, StoreAndGetHashes) {
  base::Value value("test_value");
  std::string path = "test.pref";
  std::optional<std::string> stored_mac;
  std::optional<std::string> stored_enc_b64;

  // Store both hashes using transaction with encryptor
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    tx->StoreHash(path, &value);
    tx->StoreEncryptedHash(path, &value);
    stored_mac = tx->GetMac(path);
    stored_enc_b64 = tx->GetEncryptedHash(path);
  }

  // Verify the transaction stored non-empty hashes
  ASSERT_TRUE(stored_mac.has_value());
  ASSERT_FALSE(stored_mac->empty());
  ASSERT_TRUE(stored_enc_b64.has_value());
  ASSERT_FALSE(stored_enc_b64->empty());

  // Verify storage in contents directly.
  VerifyStoredHashes(path, stored_mac, stored_enc_b64);

  // Verify retrieval via a new transaction matches what was stored
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    EXPECT_TRUE(tx->HasHash(path));
    EXPECT_TRUE(tx->HasEncryptedHash(path));
    EXPECT_EQ(stored_mac, tx->GetMac(path));
    EXPECT_EQ(stored_enc_b64, tx->GetEncryptedHash(path));
  }
}

TEST_F(PrefHashStoreImplEncryptedTest, StoreHashOnly) {
  base::Value value("mac_only_value");
  std::string path = "mac.only.pref";
  std::optional<std::string> stored_mac;

  // Store only MAC hash
  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    tx->StoreHash(path, &value);
    stored_mac = tx->GetMac(path);
  }

  ASSERT_TRUE(stored_mac.has_value());
  ASSERT_FALSE(stored_mac->empty());

  // Verify storage in contents
  VerifyStoredHashes(path, stored_mac, std::nullopt);

  // Verify retrieval via transaction
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    EXPECT_TRUE(tx->HasHash(path));
    EXPECT_FALSE(tx->HasEncryptedHash(path));
    EXPECT_EQ(stored_mac, tx->GetMac(path));
    EXPECT_EQ(std::nullopt, tx->GetEncryptedHash(path));
  }
}

TEST_F(PrefHashStoreImplEncryptedTest, CheckValueValidation) {
  base::Value value("test_value");
  base::Value wrong_value("wrong_value");
  const base::Value* null_value_ptr = nullptr;
  std::string path = "check.pref";

  // Test with encryptor.
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    ASSERT_TRUE(tx);
    MakeSuperMACInvalid();

    // Scenario 1: Store both, check valid, check wrong, check null
    tx->StoreHash(path, &value);
    tx->StoreEncryptedHash(path, &value);
    EXPECT_EQ(ValueState::UNCHANGED_ENCRYPTED, tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::CHANGED_ENCRYPTED,
              tx->CheckValue(path, &wrong_value));
    EXPECT_EQ(ValueState::CLEARED_ENCRYPTED,
              tx->CheckValue(path, null_value_ptr));

    // Scenario 2: Store only MAC, check valid, check wrong, check null
    tx->ClearHash(path);
    tx->StoreHash(path, &value);
    // Encrypted missing, fallback to MAC
    EXPECT_EQ(ValueState::UNCHANGED_VIA_HMAC_FALLBACK,
              tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::CHANGED_VIA_HMAC_FALLBACK,
              tx->CheckValue(path, &wrong_value));
    EXPECT_EQ(ValueState::CLEARED_VIA_HMAC_FALLBACK,
              tx->CheckValue(path, null_value_ptr));

    // Scenario 3: Store only Encrypted, check valid, check wrong, check null
    tx->ClearHash(path);
    tx->StoreEncryptedHash(path, &value);
    // MAC missing, Encrypted OK
    EXPECT_EQ(ValueState::UNCHANGED_ENCRYPTED, tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::CHANGED_ENCRYPTED,
              tx->CheckValue(path, &wrong_value));
    EXPECT_EQ(ValueState::CLEARED_ENCRYPTED,
              tx->CheckValue(path, null_value_ptr));

    // Scenario 4: Store invalid Encrypted, valid MAC -> CHANGED (Enc preferred)
    tx->ClearHash(path);
    tx->StoreHash(path, &value);
    // Manually seed bad data.
    dictionary_contents_.SetMac(GetEncKey(path), "Invalid Base64");
    EXPECT_EQ(ValueState::CHANGED_ENCRYPTED, tx->CheckValue(path, &value));

    // Scenario 5: Store MAC for null, check null, check value.
    tx->ClearHash(path);
    tx->StoreHash(path, null_value_ptr);
    tx->StoreEncryptedHash(path, null_value_ptr);
    EXPECT_EQ(ValueState::UNCHANGED_ENCRYPTED,
              tx->CheckValue(path, null_value_ptr));
    EXPECT_EQ(ValueState::CHANGED_ENCRYPTED, tx->CheckValue(path, &value));

    // Scenario 6: No Hashes stored, SuperMAC invalid
    tx->ClearHash(path);
    MakeSuperMACInvalid();
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              tx->CheckValue(path, null_value_ptr));

    // Scenario 7: No Hashes stored, SuperMAC valid
    MakeSuperMACValid();
    // Stamp based on current transaction state
    ASSERT_TRUE(tx->StampSuperMac());
    ASSERT_TRUE(tx->IsSuperMACValid());
    EXPECT_EQ(ValueState::TRUSTED_UNKNOWN_VALUE, tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::TRUSTED_NULL_VALUE,
              tx->CheckValue(path, null_value_ptr));
  }

  // Test without encryptor.
  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    ASSERT_TRUE(tx);
    MakeSuperMACInvalid();

    // Scenario 1: Store only MAC (cannot store encrypted), check valid, check
    // wrong, check null
    tx->StoreHash(path, &value);
    EXPECT_EQ(ValueState::UNCHANGED, tx->CheckValue(path, &value));
    EXPECT_EQ(ValueState::CHANGED, tx->CheckValue(path, &wrong_value));
    EXPECT_EQ(ValueState::CLEARED, tx->CheckValue(path, null_value_ptr));

    // Scenario 2: Store MAC for null, check null, check value
    tx->ClearHash(path);
    tx->StoreHash(path, null_value_ptr);
    EXPECT_EQ(ValueState::UNCHANGED, tx->CheckValue(path, null_value_ptr));
    EXPECT_EQ(ValueState::CHANGED, tx->CheckValue(path, &value));

    // Scenario 3: Simulate Encrypted Hash only present (can't store this way
    // w/o encryptor).
    tx->ClearHash(path);
    std::string enc_only_hash = base::Base64Encode("OnlyEncryptedData");
    dictionary_contents_.SetMac(GetEncKey(path), enc_only_hash);
    EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE,
              tx->CheckValue(path, &value));

    // Scenario 4: Simulate Invalid Enc, Valid MAC present
    tx->ClearHash(path);
    tx->StoreHash(path, &value);
    dictionary_contents_.SetMac(GetEncKey(path), "Invalid Base64");
    EXPECT_EQ(ValueState::UNCHANGED, tx->CheckValue(path, &value));
  }
}

TEST_F(PrefHashStoreImplEncryptedTest, ClearHashTest) {
  base::Value value("value");
  std::string path = "clear.test";
  std::optional<std::string> stored_mac;
  std::optional<std::string> stored_enc_b64;

  // Store both using transaction
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    tx->StoreHash(path, &value);
    tx->StoreEncryptedHash(path, &value);
    stored_mac = tx->GetMac(path);
    stored_enc_b64 = tx->GetEncryptedHash(path);
  }
  ASSERT_TRUE(stored_mac.has_value());
  ASSERT_TRUE(stored_enc_b64.has_value());
  ASSERT_TRUE(dictionary_contents_.GetMac(path, nullptr));
  ASSERT_TRUE(dictionary_contents_.GetMac(GetEncKey(path), nullptr));

  // Clear via transaction
  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    tx->ClearHash(path);
  }
  // Verify cleared from contents using direct check
  EXPECT_FALSE(dictionary_contents_.GetMac(path, nullptr));
  EXPECT_FALSE(dictionary_contents_.GetMac(GetEncKey(path), nullptr));
}

TEST_F(PrefHashStoreImplEncryptedTest, CheckSplitValueEncryptedPathValidation) {
  const std::string kPrefPath = "my.split.pref.encrypted";
  std::vector<std::string> actual_invalid_keys;

  // Helper lambda to run a specific test case scenario
  auto run_scenario =
      [&](const std::string& scenario_name,
          const base::Value::Dict* current_pref_dict_ptr,
          const base::Value::Dict& original_values_for_hashing,
          ValueState expected_state,
          const std::vector<std::string>& expected_invalid_key_list) {
        SCOPED_TRACE("Scenario: " + scenario_name);

        pref_store_contents_.clear();
        dictionary_contents_.Reset();
        actual_invalid_keys.clear();
        MakeSuperMACInvalid();
        // 1. Seed the encrypted hashes into dictionary_contents_
        // These are the "stored" hashes.
        base::Value::Dict computed_split_encrypted_hashes =
            hash_store_.ComputeSplitEncryptedHashes(
                kPrefPath, &original_values_for_hashing, &test_encryptor_);
        SeedSplitEncryptedHashes(kPrefPath, &computed_split_encrypted_hashes);

        // 2. Ensure no MACs are present for this path to isolate the encrypted
        // path
        SeedSplitMacs(kPrefPath, nullptr);

        // 3. Perform CheckSplitValue with encryptor present
        auto tx = BeginTransaction(/*with_encryptor=*/true);
        ValueState result_state = tx->CheckSplitValue(
            kPrefPath, current_pref_dict_ptr, &actual_invalid_keys);

        // 4. Verify results
        EXPECT_EQ(expected_state, result_state);
        std::sort(actual_invalid_keys.begin(), actual_invalid_keys.end());
        std::vector<std::string> sorted_expected_keys =
            expected_invalid_key_list;
        std::sort(sorted_expected_keys.begin(), sorted_expected_keys.end());
        EXPECT_EQ(sorted_expected_keys, actual_invalid_keys);
      };

  // --- Test Scenarios for the if (encryptor_) block ---
  // --- These tests are triggered when an encryptor_ is present ---

  // Scenario E1: All Keys Match, Hashes Valid
  base::Value::Dict s1_prefs_and_hashes;
  s1_prefs_and_hashes.Set("key1", "value1");
  s1_prefs_and_hashes.Set("key2", "value2");
  run_scenario("E1_AllValid", &s1_prefs_and_hashes, s1_prefs_and_hashes,
               ValueState::UNCHANGED_ENCRYPTED, {});

  // Scenario E2: Value Changed for One Key (Hash Invalid)
  base::Value::Dict s2_current_prefs;
  s2_current_prefs.Set("key1", "value1_MODIFIED");
  s2_current_prefs.Set("key2", "value2");
  base::Value::Dict s2_original_hashes;
  s2_original_hashes.Set("key1", "value1");
  s2_original_hashes.Set("key2", "value2");
  run_scenario("E2_OneValueChanged", &s2_current_prefs, s2_original_hashes,
               ValueState::CHANGED_ENCRYPTED, {"key1"});

  // Scenario E3: Key Added in Value (Not in Stored Hashes)
  base::Value::Dict s3_current_prefs;
  s3_current_prefs.Set("key1", "value1");
  s3_current_prefs.Set("key2", "value2");
  base::Value::Dict s3_original_hashes;
  s3_original_hashes.Set("key1", "value1");
  run_scenario("E3_KeyAddedInValue", &s3_current_prefs, s3_original_hashes,
               ValueState::CHANGED_ENCRYPTED, {"key2"});

  // Scenario E4: Key Removed from Value (Present in Stored Hashes)
  base::Value::Dict s4_current_prefs;
  s4_current_prefs.Set("key1", "value1");
  base::Value::Dict s4_original_hashes;
  s4_original_hashes.Set("key1", "value1");
  s4_original_hashes.Set("key2", "value2");
  run_scenario("E4_KeyRemovedFromValue", &s4_current_prefs, s4_original_hashes,
               ValueState::CHANGED_ENCRYPTED, {"key2"});

  // Scenario E5: Multiple Invalidities (Value Change, Key Added, Key Removed)
  base::Value::Dict s5_current_prefs;
  s5_current_prefs.Set("keyA", "valueA_MODIFIED");
  s5_current_prefs.Set("keyC", "valueC");
  base::Value::Dict s5_original_hashes;
  s5_original_hashes.Set("keyA", "valueA");
  s5_original_hashes.Set("keyB", "valueB");
  run_scenario("E5_MultipleInvalidities", &s5_current_prefs, s5_original_hashes,
               ValueState::CHANGED_ENCRYPTED, {"keyA", "keyB", "keyC"});

  // Scenario E6: Initial Value is Empty, Stored Encrypted Hashes Exist
  base::Value::Dict s6_original_hashes;
  s6_original_hashes.Set("key1", "value1");
  base::Value::Dict s6_empty_current_prefs;
  run_scenario("E6_EmptyValue_HashesExist", &s6_empty_current_prefs,
               s6_original_hashes, ValueState::CLEARED_ENCRYPTED, {"key1"});

  // Scenario E6b: Initial Value is Null, Stored Encrypted Hashes Exist
  run_scenario("E6b_NullValue_HashesExist", nullptr, s6_original_hashes,
               ValueState::CLEARED_ENCRYPTED, {"key1"});

  // --- Scenario E7: Initial Value Exists, No Stored Encrypted Hashes (empty
  // map of seed hashes) ---
  base::Value::Dict s7_current_prefs;
  s7_current_prefs.Set("key1", "value1");
  base::Value::Dict s7_empty_original_hashes;
  run_scenario("E7_ValueExists_NoHashesStored", &s7_current_prefs,
               s7_empty_original_hashes, ValueState::UNTRUSTED_UNKNOWN_VALUE,
               {});

  // --- Scenario E8: Initial Value Exists, Stored Hash Dictionary is Empty ---
  {
    SCOPED_TRACE("Scenario: E8_ValueExists_EmptyStoredHashDict");
    pref_store_contents_.clear();
    dictionary_contents_.Reset();
    actual_invalid_keys.clear();
    MakeSuperMACInvalid();

    base::Value::Dict s8_current_prefs;
    s8_current_prefs.Set("keyA", "valueA");
    s8_current_prefs.Set("keyB", "valueB");

    // 1. Directly create an empty dictionary for the split encrypted hashes
    //    at the correct nested path within pref_store_contents_.
    std::string enc_base_key_for_split = GetSplitEncKeyBase(kPrefPath);
    std::string full_dotted_path = "protection.macs." + enc_base_key_for_split;
    pref_store_contents_.SetByDottedPath(full_dotted_path, base::Value::Dict());
    // ^^^ Creates an empty dictionary at the full path

    // 2. Ensure no MACs are present for this path
    SeedSplitMacs(kPrefPath, nullptr);

    // 3. Perform CheckSplitValue with encryptor present
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    ValueState result_state =
        tx->CheckSplitValue(kPrefPath, &s8_current_prefs, &actual_invalid_keys);

    // 4. Verify results
    // Expected: CHANGED, because current pref has keys, but stored hash dict is
    // empty.
    EXPECT_EQ(ValueState::CHANGED_ENCRYPTED, result_state);
    std::vector<std::string> expected_bad_keys_s8 = {"keyA", "keyB"};
    std::sort(actual_invalid_keys.begin(), actual_invalid_keys.end());
    std::sort(expected_bad_keys_s8.begin(), expected_bad_keys_s8.end());
    EXPECT_EQ(expected_bad_keys_s8, actual_invalid_keys);
  }
}

TEST_F(PrefHashStoreImplEncryptedTest, ComputeSplitEncryptedHashes) {
  const std::string kBasePath = "my.split.pref";

  // Scenario 1: Null split_values
  base::Value::Dict result1 = hash_store_.ComputeSplitEncryptedHashes(
      kBasePath, nullptr, &test_encryptor_);
  EXPECT_TRUE(result1.empty());

  // Scenario 2: Empty split_values dictionary
  base::Value::Dict empty_dict;
  base::Value::Dict result2 = hash_store_.ComputeSplitEncryptedHashes(
      kBasePath, &empty_dict, &test_encryptor_);
  EXPECT_TRUE(result2.empty());

  // Scenario 3: Null encryptor
  base::Value::Dict input_dict3;
  input_dict3.Set("key1", "value1");
  base::Value::Dict result3 =
      hash_store_.ComputeSplitEncryptedHashes(kBasePath, &input_dict3, nullptr);
  EXPECT_TRUE(result3.empty());

  // Scenario 4: Valid split_values and encryptor - Functional Test
  base::Value::Dict input_dict4;
  input_dict4.Set("sub1", "alpha");
  input_dict4.Set("sub2", 123);

  base::Value::Dict computed_hashes_for_dict4 =
      hash_store_.ComputeSplitEncryptedHashes(kBasePath, &input_dict4,
                                              &test_encryptor_);

  // Assertions for Scenario 4: Check structure and functional validity
  ASSERT_EQ(2u, computed_hashes_for_dict4.size());
  const std::string* hash_sub1_s4 =
      computed_hashes_for_dict4.FindString("sub1");
  const std::string* hash_sub2_s4 =
      computed_hashes_for_dict4.FindString("sub2");
  ASSERT_TRUE(hash_sub1_s4);
  ASSERT_TRUE(hash_sub2_s4);
  EXPECT_FALSE(hash_sub1_s4->empty());
  EXPECT_FALSE(hash_sub2_s4->empty());

  // Verify these hashes are usable by CheckSplitValue
  SeedSplitEncryptedHashes(kBasePath, &computed_hashes_for_dict4);
  SeedSplitMacs(kBasePath, nullptr);
  MakeSuperMACValid();
  {
    auto tx = BeginTransaction(true);
    std::vector<std::string> invalid_keys;
    EXPECT_EQ(ValueState::UNCHANGED_ENCRYPTED,
              tx->CheckSplitValue(kBasePath, &input_dict4, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }

  // Scenario 5: One sub-item is binary
  // Assuming production code hashes non-serializable (like binary) as if it
  // were an empty string.
  base::Value::Dict input_dict5;
  input_dict5.Set("good_key1", "good_value1");
  std::vector<uint8_t> binary_data = {0, 1, 2};
  input_dict5.Set("bad_key_binary", base::Value(binary_data));
  input_dict5.Set("good_key2", "another_value");

  base::Value::Dict computed_hashes_for_dict5 =
      hash_store_.ComputeSplitEncryptedHashes(kBasePath, &input_dict5,
                                              &test_encryptor_);

  // Assertions for Scenario 5 - EXPECTING bad_key_binary TO BE HASHED
  ASSERT_EQ(3u, computed_hashes_for_dict5.size())
      << "Expected all keys to be present if binary is hashed (e.g., as empty "
         "string).";
  EXPECT_TRUE(computed_hashes_for_dict5.contains("good_key1"));
  EXPECT_TRUE(computed_hashes_for_dict5.contains("good_key2"));
  EXPECT_TRUE(computed_hashes_for_dict5.contains("bad_key_binary"));

  const std::string* hash_good1_s5 =
      computed_hashes_for_dict5.FindString("good_key1");
  const std::string* hash_bad_s5 =
      computed_hashes_for_dict5.FindString("bad_key_binary");
  const std::string* hash_good2_s5 =
      computed_hashes_for_dict5.FindString("good_key2");
  ASSERT_TRUE(hash_good1_s5 && !hash_good1_s5->empty());
  ASSERT_TRUE(hash_bad_s5 && !hash_bad_s5->empty());
  ASSERT_TRUE(hash_good2_s5 && !hash_good2_s5->empty());

  // Verify these hashes functionally with CheckSplitValue
  // CheckSplitValue will re-calculate hashes for good_key1, bad_key_binary (as
  // empty string), good_key2 and they should match the stored ones.
  SeedSplitEncryptedHashes(kBasePath, &computed_hashes_for_dict5);
  SeedSplitMacs(kBasePath, nullptr);
  MakeSuperMACValid();
  {
    auto tx = BeginTransaction(true);
    std::vector<std::string> invalid_keys;
    EXPECT_EQ(ValueState::UNCHANGED_ENCRYPTED,
              tx->CheckSplitValue(kBasePath, &input_dict5, &invalid_keys));
    EXPECT_TRUE(invalid_keys.empty());
  }
}

// For PrefHashStoreImpl::ComputeEncryptedHash(..., Dict*, ...)
TEST_F(PrefHashStoreImplEncryptedTest, ComputeEncryptedHash_ForDict_Success) {
  const std::string kPath = "test.dict.pref.compute";
  base::Value::Dict test_dict;
  test_dict.Set("d_key1", "d_value1");
  test_dict.Set("d_key2", 456);

  // Assuming test_encryptor_instance_ works by default.
  std::string encrypted_hash_str =
      hash_store_.ComputeEncryptedHash(kPath, &test_dict, &test_encryptor_);
  EXPECT_FALSE(encrypted_hash_str.empty());
  std::string decoded_once;
  EXPECT_TRUE(base::Base64Decode(encrypted_hash_str, &decoded_once))
      << "Result should be Base64";
}

// For PrefHashStoreTransactionImpl::StoreEncryptedHash - no encryptor block.
TEST_F(PrefHashStoreImplEncryptedTest,
       StoreEncryptedHash_WhenNoEncryptor_DoesNotStore) {
  const std::string kPath = "test.no.encryptor.store.eh";
  const std::string kEncKey = GetEncKey(kPath);
  base::Value test_value("value to try storing");
  SeedAtomicMac(kPath, "some_existing_mac");

  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    tx->StoreEncryptedHash(kPath, &test_value);

    EXPECT_FALSE(dictionary_contents_.GetMac(kEncKey, nullptr))
        << "Encrypted hash should not be stored.";
    VerifyStoredHashes(kPath, "some_existing_mac", std::nullopt);
  }
}

// PrefHashStoreTransactionImpl::CheckSplitValueInternal - loop for removed keys
// & no-encryptor fallback block.
TEST_F(PrefHashStoreImplEncryptedTest,
       CheckSplitValue_EncryptorOn_KeyPresentInStoreMissingInValue) {
  const std::string kPath = "split.eh.key_removed_from_value";
  base::Value::Dict original_seeded_values;
  original_seeded_values.Set("keyA", "valA");
  original_seeded_values.Set("keyB_in_store_only", "valB");
  SeedSplitEncryptedHashesFromValues(kPath, &original_seeded_values);
  SeedSplitMacs(kPath, nullptr);

  base::Value::Dict current_pref_dict;
  current_pref_dict.Set("keyA", "valA");

  auto tx = BeginTransaction(/*with_encryptor=*/true);
  std::vector<std::string> invalid_keys;
  ValueState state =
      tx->CheckSplitValue(kPath, &current_pref_dict, &invalid_keys);

  EXPECT_EQ(ValueState::CHANGED_ENCRYPTED, state);
  ASSERT_EQ(1u, invalid_keys.size());
  EXPECT_EQ("keyB_in_store_only", invalid_keys[0]);
}

TEST_F(PrefHashStoreImplEncryptedTest,
       CheckSplitValue_NoEncryptor_EHExists_NoMACs_ResultsInUntrusted) {
  const std::string kPath = "split.no_encryptor.only_eh_unusable";
  base::Value::Dict values_for_eh;
  values_for_eh.Set("key1", "value1");
  SeedSplitEncryptedHashesFromValues(kPath, &values_for_eh);
  SeedSplitMacs(kPath, nullptr);
  MakeSuperMACInvalid();

  base::Value::Dict current_pref_dict = values_for_eh.Clone();

  auto tx = BeginTransaction(/*with_encryptor=*/false);
  std::vector<std::string> invalid_keys;
  ValueState state =
      tx->CheckSplitValue(kPath, &current_pref_dict, &invalid_keys);

  EXPECT_EQ(ValueState::UNTRUSTED_UNKNOWN_VALUE, state);
  EXPECT_TRUE(invalid_keys.empty());
}

TEST_F(PrefHashStoreImplEncryptedTest,
       CheckSplitValue_NoEncryptor_EHAndMACsExist_UsesMACs) {
  const std::string kPath = "split.no_encryptor.eh_and_macs";
  base::Value::Dict base_values;
  base_values.Set("key1", "value1");
  base_values.Set("key2", "value2");

  base::Value::Dict eh_seed_values;
  eh_seed_values.Set("key1", "value1");
  SeedSplitEncryptedHashesFromValues(kPath, &eh_seed_values);

  SeedSplitMacs(kPath, &base_values);
  MakeSuperMACInvalid();

  base::Value::Dict current_pref_dict1 = base_values.Clone();
  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    std::vector<std::string> invalid_keys;
    ValueState state =
        tx->CheckSplitValue(kPath, &current_pref_dict1, &invalid_keys);
    EXPECT_EQ(ValueState::UNCHANGED, state)
        << "Should rely on MACs when encryptor is off";
    EXPECT_TRUE(invalid_keys.empty());
  }

  base::Value::Dict current_pref_dict2 = base_values.Clone();
  current_pref_dict2.Set("key2", "value2_changed");
  {
    auto tx = BeginTransaction(/*with_encryptor=*/false);
    std::vector<std::string> invalid_keys;
    ValueState state =
        tx->CheckSplitValue(kPath, &current_pref_dict2, &invalid_keys);
    EXPECT_EQ(ValueState::CHANGED, state);
    ASSERT_EQ(1u, invalid_keys.size());
    EXPECT_EQ("key2", invalid_keys[0]);
  }
}

// For PrefHashStoreTransactionImpl::StoreSplitEncryptedHash - private method
TEST_F(PrefHashStoreImplEncryptedTest,
       StoreSplitEncryptedViaIteratedAtomicStores_NoEncryptor) {
  const std::string kBasePath = "my.split.base.noenc.store";
  const std::string kSubKey1 = "subkey1";
  const std::string kFullSubPath1 = kBasePath + "." + kSubKey1;

  auto tx = BeginTransaction(/*with_encryptor=*/false);
  base::Value sub_value1("value1");
  tx->StoreEncryptedHash(kFullSubPath1, &sub_value1);

  EXPECT_FALSE(tx->HasEncryptedHash(kFullSubPath1));
  VerifyStoredHashes(kFullSubPath1, std::nullopt, std::nullopt);
}

TEST_F(PrefHashStoreImplEncryptedTest,
       StoreSplitEncryptedViaIteratedAtomicStores_ClearsOldBaseAtomics) {
  const std::string kBasePath = "my.split.base.clearcheck";
  SeedAtomicEncryptedHash(kBasePath, "old_atomic_base_eh_b64");

  const std::string kSubKey1 = "subkey1";
  const std::string kFullSubPath1 = kBasePath + "." + kSubKey1;
  base::Value sub_value1("value1_for_split_part");

  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    tx->StoreEncryptedHash(kFullSubPath1, &sub_value1);
  }

  VerifyStoredHashes(kBasePath, std::nullopt, "old_atomic_base_eh_b64");
  EXPECT_TRUE(BeginTransaction(true)->HasEncryptedHash(kFullSubPath1));
}

// For PrefHashStoreTransactionImpl::ImportHash
TEST_F(PrefHashStoreImplEncryptedTest,
       ImportHash_StringMac_ClearsOldEH_AndStampsSuperMACIfValid) {
  const std::string kPath = "import.string.mac.specific";
  SeedAtomicEncryptedHash(kPath, "eh_to_be_cleared_b64");
  MakeSuperMACValid();

  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    ASSERT_TRUE(tx->IsSuperMACValid());

    base::Value mac_to_import("newly_imported_mac");
    tx->ImportHash(kPath, &mac_to_import);

    VerifyStoredHashes(kPath, "newly_imported_mac", std::nullopt);
    EXPECT_TRUE(tx->StampSuperMac());
    EXPECT_TRUE(tx->IsSuperMACValid());
  }
}

TEST_F(PrefHashStoreImplEncryptedTest,
       ImportHash_Dict_MacOnly_ClearsOldEH_AndStampsSuperMACIfValid) {
  const std::string kPath = "import.dict.maconly.specific";
  SeedAtomicEncryptedHash(kPath, "eh_to_be_cleared_b64");
  SeedAtomicMac(kPath, "old_mac_to_be_overwritten");
  MakeSuperMACValid();

  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    ASSERT_TRUE(tx->IsSuperMACValid());

    base::Value::Dict dict_to_import_data;
    dict_to_import_data.Set(kImportMacKey, "imported_dict_mac");
    base::Value value_for_import(dict_to_import_data.Clone());
    tx->ImportHash(kPath, &value_for_import);

    VerifyStoredHashes(kPath, "imported_dict_mac", std::nullopt);
    EXPECT_TRUE(tx->StampSuperMac());
    EXPECT_TRUE(tx->IsSuperMACValid());
  }
}

TEST_F(PrefHashStoreImplEncryptedTest,
       ImportHash_Dict_EHOnly_ClearsOldMac_AndStampsSuperMACIfValid) {
  const std::string kPath = "import.dict.ehonly.specific";
  SeedAtomicMac(kPath, "mac_to_be_cleared");
  SeedAtomicEncryptedHash(kPath, "old_eh_to_be_overwritten_b64");
  MakeSuperMACValid();

  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    ASSERT_TRUE(tx->IsSuperMACValid());

    base::Value::Dict dict_to_import_data;
    dict_to_import_data.Set(kImportEncryptedHashKey, "imported_dict_eh_b64");
    base::Value value_for_import(dict_to_import_data.Clone());
    tx->ImportHash(kPath, &value_for_import);

    VerifyStoredHashes(kPath, std::nullopt, "imported_dict_eh_b64");
    EXPECT_TRUE(tx->StampSuperMac());
    EXPECT_TRUE(tx->IsSuperMACValid());
  }
}

TEST_F(
    PrefHashStoreImplEncryptedTest,
    ImportHash_Dict_NoRelevantKeys_NoOldHashes_SuperMACValid_StampsSuperMAC) {
  const std::string kPath = "import.dict.norelevant.noold";
  dictionary_contents_.RemoveEntry(kPath);
  dictionary_contents_.RemoveEntry(GetEncKey(kPath));
  MakeSuperMACValid();

  auto tx = BeginTransaction(/*with_encryptor=*/true);
  ASSERT_TRUE(tx->IsSuperMACValid());
  // Initial StampSuperMac will be true because the transaction's
  // super_mac_dirty_ is set by constructor if it reads a valid MAC (or by
  // StampSuperMac if it had to make it valid). Given the provided StampSuperMac
  // impl, it will always be true here.
  EXPECT_TRUE(tx->StampSuperMac())
      << "First stamp will always be true if use_super_mac is on.";

  base::Value::Dict dict_to_import_data;
  dict_to_import_data.Set("some_other_key", "some_value");
  base::Value value_for_import(dict_to_import_data.Clone());
  tx->ImportHash(kPath, &value_for_import);

  VerifyStoredHashes(kPath, std::nullopt, std::nullopt);

  EXPECT_TRUE(tx->StampSuperMac())
      << "Importing dict when SuperMAC was valid makes it stampable again.";
  EXPECT_TRUE(tx->IsSuperMACValid());
}

TEST_F(PrefHashStoreImplEncryptedTest,
       ImportHash_OtherType_NoChange_NoStampIfWasClean) {
  const std::string kPath = "import.othertype.nochange";
  SeedAtomicMac(kPath, "old_mac_data");
  MakeSuperMACValid();

  auto tx = BeginTransaction(/*with_encryptor=*/true);
  ASSERT_TRUE(tx->IsSuperMACValid());
  tx->StampSuperMac();

  base::Value int_value(123);
  tx->ImportHash(kPath, &int_value);

  VerifyStoredHashes(kPath, "old_mac_data", std::nullopt);
  EXPECT_TRUE(
      tx->StampSuperMac());  // This will be true with the current StampSuperMac
  EXPECT_TRUE(tx->IsSuperMACValid());
}

// For PrefHashStoreTransactionImpl::ClearEncryptedHash - private, tested via
// public ClearHash.
TEST_F(PrefHashStoreImplEncryptedTest,
       ClearHash_TargetingPrivateClearEncryptedHashLogic) {
  const std::string kPath = "clear.eh.via.clearhash";
  const std::string kEncKey = GetEncKey(kPath);

  // Scenario 1: Both exist, SuperMAC valid
  SeedAtomicEncryptedHash(kPath, "eh_data_b64");
  SeedAtomicMac(kPath, "mac_data_also");
  MakeSuperMACValid();
  auto tx1 = BeginTransaction(/*with_encryptor=*/true);
  ASSERT_TRUE(tx1->IsSuperMACValid());

  tx1->ClearHash(kPath);

  EXPECT_FALSE(dictionary_contents_.GetMac(kPath, nullptr));
  EXPECT_FALSE(dictionary_contents_.GetMac(kEncKey, nullptr));
  EXPECT_TRUE(tx1->StampSuperMac());
  EXPECT_TRUE(tx1->IsSuperMACValid());

  // Scenario 2: Both exist, SuperMAC invalid
  SeedAtomicEncryptedHash(kPath, "eh_data_b64_sm_invalid");
  SeedAtomicMac(kPath, "mac_data_sm_invalid");
  MakeSuperMACInvalid();
  auto tx2 = BeginTransaction(/*with_encryptor=*/true);
  ASSERT_FALSE(tx2->IsSuperMACValid());

  tx2->ClearHash(kPath);

  EXPECT_FALSE(dictionary_contents_.GetMac(kEncKey, nullptr));
  EXPECT_FALSE(dictionary_contents_.GetMac(kPath, nullptr));
  EXPECT_TRUE(tx2->StampSuperMac());
  EXPECT_TRUE(tx2->IsSuperMACValid());

  // Scenario 3: Neither EH nor MAC exists, SuperMAC valid
  dictionary_contents_.RemoveEntry(kPath);
  dictionary_contents_.RemoveEntry(GetEncKey(kPath));
  MakeSuperMACValid();
  auto tx3 = BeginTransaction(/*with_encryptor=*/true);
  ASSERT_TRUE(tx3->IsSuperMACValid());
  tx3->StampSuperMac();

  tx3->ClearHash(kPath);

  // If StampSuperMac always returns true, we can't differentiate a "no-op
  // clean" from "op dirty". We can only check that contents are still empty.
  VerifyStoredHashes(kPath, std::nullopt, std::nullopt);
  EXPECT_TRUE(tx3->StampSuperMac());
}

TEST_F(
    PrefHashStoreImplEncryptedTest,
    StoreSplitEncryptedHash_ClearsOldSplitEncHashes_KeepsLegacyMAC_StoresNew) {
  const std::string kPath = "split.clear.old.split.keepmac";
  const std::string kEncKeyForSplitHashes = GetEncKey(kPath);

  // 1. Seed a legacy MAC for the base path.
  const std::string kLegacyMacValue = "legacy_mac_for_split_test";
  SeedAtomicMac(kPath, kLegacyMacValue);
  std::string temp_check_val;
  ASSERT_TRUE(dictionary_contents_.GetMac(kPath, &temp_check_val));
  ASSERT_EQ(kLegacyMacValue, temp_check_val);

  // 2. Seed old split encrypted hashes.
  base::Value::Dict old_split_content;
  old_split_content.Set("old_subkey", "old_value");
  old_split_content.Set("common_subkey", "common_value_old_hash");
  base::Value::Dict old_computed_split_ehs =
      hash_store_.ComputeSplitEncryptedHashes(kPath, &old_split_content,
                                              &test_encryptor_);
  SeedSplitEncryptedHashes(kPath, &old_computed_split_ehs);

  // Verify old split EHs are present.
  std::map<std::string, std::string> temp_split_ehs;
  ASSERT_TRUE(dictionary_contents_.GetSplitMacs(kEncKeyForSplitHashes,
                                                &temp_split_ehs));
  ASSERT_TRUE(temp_split_ehs.count("old_subkey"));

  // 3. Prepare new split value.
  base::Value::Dict new_split_content;
  new_split_content.Set("new_subkey", "new_value");
  new_split_content.Set("common_subkey", "common_value_new_hash");

  // 4. Perform StoreSplitEncryptedHash transaction.
  {
    auto tx = BeginTransaction(/*with_encryptor=*/true);
    tx->StoreSplitEncryptedHash(kPath, &new_split_content);
  }

  // 5. Verify the legacy MAC is still present.
  EXPECT_TRUE(dictionary_contents_.GetMac(kPath, &temp_check_val));
  EXPECT_EQ(kLegacyMacValue, temp_check_val);

  // 6. Verify new split encrypted hashes are stored and old ones are gone.
  std::map<std::string, std::string> stored_split_ehs;
  ASSERT_TRUE(dictionary_contents_.GetSplitMacs(kEncKeyForSplitHashes,
                                                &stored_split_ehs));
  EXPECT_FALSE(stored_split_ehs.count("old_subkey"));
  EXPECT_TRUE(stored_split_ehs.count("new_subkey"));
  EXPECT_FALSE(stored_split_ehs.at("new_subkey").empty());
  EXPECT_TRUE(stored_split_ehs.count("common_subkey"));
  // common_subkey's hash should have been updated.
  EXPECT_NE(temp_split_ehs.at("common_subkey"),
            stored_split_ehs.at("common_subkey"));
}

#if BUILDFLAG(IS_WIN)
TEST_F(PrefHashStoreImplEncryptedTest,
       CheckValueEnterpriseRoamingHmacOnWindows) {
  const std::string kPath = "enterprise.roaming.pref";
  base::Value value("enterprise_value");

  SeedAtomicMac(kPath, hash_store_.ComputeMac(kPath, &value));
  SeedAtomicEncryptedHash(
      kPath, hash_store_.ComputeEncryptedHash(kPath, &value, &test_encryptor_));
  SeedAtomicMac(kPath, "invalid_roaming_mac");

  std::optional<base::AutoReset<bool>> is_enterprise_device_for_testing_ =
      base::SetIsEnterpriseDeviceForTesting(true);

  // Perform CheckValue without the encryptor.
  auto tx = BeginTransaction(/*with_encryptor=*/false);
  ValueState state = tx->CheckValue(kPath, &value);

  // On an enterprise device, the presence of an encrypted hash should cause
  // the transaction to defer validation.
  EXPECT_EQ(ValueState::UNCHANGED, state);

  is_enterprise_device_for_testing_.reset();
}
#endif