text: // Copyright 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/chromeos/policy/cloud_external_data_store.h"
  
  #include <stddef.h>
  
  #include <memory>
  
  #include "base/compiler_specific.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/macros.h"
  #include "base/optional.h"
  #include "base/test/test_simple_task_runner.h"
  #include "components/policy/core/common/cloud/resource_cache.h"
  #include "crypto/sha2.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  namespace policy {
  
  namespace {
  
  const char kKey1[] = "Key 1";
  const char kKey2[] = "Key 2";
  const char kPolicy1[] = "Test policy 1";
  const char kPolicy2[] = "Test policy 2";
  const char kData1[] = "Testing data 1";
  const char kData2[] = "Testing data 2";
  const char kURL[] = "http://localhost";
  const size_t kMaxSize = 100;
  
  }  // namespace
  
  class CouldExternalDataStoreTest : public testing::Test {
   public:
    CouldExternalDataStoreTest();
  
    void SetUp() override;
  
   protected:
    const std::string kData1Hash;
    const std::string kData2Hash;
  
    scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
    base::ScopedTempDir temp_dir_;
    std::unique_ptr<ResourceCache> resource_cache_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(CouldExternalDataStoreTest);
  };
  
  CouldExternalDataStoreTest::CouldExternalDataStoreTest()
      : kData1Hash(crypto::SHA256HashString(kData1)),
        kData2Hash(crypto::SHA256HashString(kData2)),
        task_runner_(new base::TestSimpleTaskRunner) {
  }
  
  void CouldExternalDataStoreTest::SetUp() {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    resource_cache_.reset(new ResourceCache(temp_dir_.GetPath(), task_runner_,
                                            /* max_cache_size */ base::nullopt));
  }
  
  TEST_F(CouldExternalDataStoreTest, StoreAndLoad) {
    // Write an entry to a store.
    CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData1).empty());
  
    // Check that loading and verifying the entry against an invalid hash fails.
    std::string data;
    EXPECT_TRUE(store.Load(kPolicy1, kData2Hash, kMaxSize, &data).empty());
  
    // Check that loading and verifying the entry against its hash succeeds.
    EXPECT_FALSE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
    EXPECT_EQ(kData1, data);
  }
  
  TEST_F(CouldExternalDataStoreTest, StoreTooLargeAndLoad) {
    // Write an entry to a store.
    CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData2).empty());
  
    // Check that the entry has been written to the resource cache backing the
    // store.
    std::map<std::string, std::string> contents;
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData2, contents.begin()->second);
  
    // Check that loading the entry fails when the maximum allowed data size is
    // smaller than the entry size.
    std::string data;
    EXPECT_TRUE(store.Load(kPolicy1, kData1Hash, 1, &data).empty());
  
    // Verify that the oversized entry has been detected and removed from the
    // resource cache.
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    EXPECT_TRUE(contents.empty());
  }
  
  TEST_F(CouldExternalDataStoreTest, StoreInvalidAndLoad) {
    // Construct a store entry whose hash and contents do not match.
    CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData2).empty());
  
    // Check that the entry has been written to the resource cache backing the
    // store.
    std::map<std::string, std::string> contents;
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData2, contents.begin()->second);
  
    // Check that loading and verifying the entry against its hash fails.
    std::string data;
    EXPECT_TRUE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
  
    // Verify that the corrupted entry has been detected and removed from the
    // resource cache.
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    EXPECT_TRUE(contents.empty());
  }
  
  TEST_F(CouldExternalDataStoreTest, Prune) {
    // Write two entries to a store.
    CloudExternalDataStore store(kKey1, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store.Store(kPolicy1, kData1Hash, kData1).empty());
    EXPECT_FALSE(store.Store(kPolicy2, kData2Hash, kData2).empty());
  
    // Check that loading and verifying the entries against their hashes succeeds.
    std::string data;
    EXPECT_FALSE(store.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
    EXPECT_EQ(kData1, data);
    EXPECT_FALSE(store.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
    EXPECT_EQ(kData2, data);
  
    // Prune the store, allowing only an entry for the first policy with its
    // current hash to be kept.
    CloudExternalDataManager::Metadata metadata;
    metadata[kPolicy1] =
        CloudExternalDataManager::MetadataEntry(kURL, kData1Hash);
    store.Prune(metadata);
  
    // Check that the entry for the second policy has been removed from the
    // resource cache backing the store.
    std::map<std::string, std::string> contents;
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData1, contents.begin()->second);
  
    // Prune the store, allowing only an entry for the first policy with a
    // different hash to be kept.
    metadata[kPolicy1] =
        CloudExternalDataManager::MetadataEntry(kURL, kData2Hash);
    store.Prune(metadata);
  
    // Check that the entry for the first policy has been removed from the
    // resource cache.
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    EXPECT_TRUE(contents.empty());
  }
  
  TEST_F(CouldExternalDataStoreTest, SharedCache) {
    // Write entries to two stores for two different cache_keys sharing a cache.
    CloudExternalDataStore store1(kKey1, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store1.Store(kPolicy1, kData1Hash, kData1).empty());
    CloudExternalDataStore store2(kKey2, task_runner_, resource_cache_.get());
    EXPECT_FALSE(store2.Store(kPolicy2, kData2Hash, kData2).empty());
  
    // Check that the entries have been assigned to the correct keys in the
    // resource cache backing the stores.
    std::map<std::string, std::string> contents;
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData1, contents.begin()->second);
    resource_cache_->LoadAllSubkeys(kKey2, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData2, contents.begin()->second);
  
    // Check that each entry can be loaded from the correct store.
    std::string data;
    EXPECT_FALSE(store1.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
    EXPECT_EQ(kData1, data);
    EXPECT_TRUE(store1.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
  
    EXPECT_TRUE(store2.Load(kPolicy1, kData1Hash, kMaxSize, &data).empty());
    EXPECT_FALSE(store2.Load(kPolicy2, kData2Hash, kMaxSize, &data).empty());
    EXPECT_EQ(kData2, data);
  
    // Prune the first store, allowing no entries to be kept.
    CloudExternalDataManager::Metadata metadata;
    store1.Prune(metadata);
  
    // Check that the part of the resource cache backing the first store is empty.
    resource_cache_->LoadAllSubkeys(kKey1, &contents);
    EXPECT_TRUE(contents.empty());
  
    // Check that the part of the resource cache backing the second store is
    // unaffected.
    resource_cache_->LoadAllSubkeys(kKey2, &contents);
    ASSERT_EQ(1u, contents.size());
    EXPECT_EQ(kData2, contents.begin()->second);
  }
  
  }  // namespace policy
  