// Copyright (c) 2012 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/settings/device_settings_provider.h"

#include <string>

#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/scoped_temp_dir.h"
#include "base/values.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/settings/cros_settings_names.h"
#include "chrome/browser/chromeos/settings/device_settings_test_helper.h"
#include "chrome/browser/chromeos/settings/mock_owner_key_util.h"
#include "chrome/browser/policy/policy_builder.h"
#include "chrome/browser/policy/proto/chrome_device_policy.pb.h"
#include "chrome/browser/policy/proto/device_management_backend.pb.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_pref_service.h"
#include "content/public/test/test_browser_thread.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace em = enterprise_management;

namespace chromeos {

using ::testing::AnyNumber;
using ::testing::Mock;
using ::testing::_;

class DeviceSettingsProviderTest: public testing::Test {
 public:
  MOCK_METHOD1(SettingChanged, void(const std::string&));
  MOCK_METHOD0(GetTrustedCallback, void(void));

 protected:
  DeviceSettingsProviderTest()
      : message_loop_(MessageLoop::TYPE_UI),
        ui_thread_(content::BrowserThread::UI, &message_loop_),
        file_thread_(content::BrowserThread::FILE, &message_loop_),
        local_state_(static_cast<TestingBrowserProcess*>(g_browser_process)),
        owner_key_util_(new MockOwnerKeyUtil()) {}

  virtual void SetUp() OVERRIDE {
    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA,
                                 &original_user_data_dir_));
    ASSERT_TRUE(user_data_dir_.CreateUniqueTempDir());
    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA,
                                      user_data_dir_.path()));
    policy_.payload().mutable_metrics_enabled()->set_metrics_enabled(false);
    policy_.Build();

    device_settings_test_helper_.set_policy_blob(policy_.GetBlob());

    device_settings_service_.Initialize(&device_settings_test_helper_,
                                        owner_key_util_);

    EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());
    provider_.reset(
        new DeviceSettingsProvider(
            base::Bind(&DeviceSettingsProviderTest::SettingChanged,
                       base::Unretained(this)),
            &device_settings_service_));
    Mock::VerifyAndClearExpectations(this);
  }

  virtual void TearDown() OVERRIDE {
    device_settings_service_.Shutdown();
    ASSERT_TRUE(PathService::Override(chrome::DIR_USER_DATA,
                                      original_user_data_dir_));
  }

  MessageLoop message_loop_;
  content::TestBrowserThread ui_thread_;
  content::TestBrowserThread file_thread_;

  ScopedStubCrosEnabler stub_cros_enabler_;

  ScopedTestingLocalState local_state_;

  DeviceSettingsTestHelper device_settings_test_helper_;
  scoped_refptr<MockOwnerKeyUtil> owner_key_util_;

  DeviceSettingsService device_settings_service_;

  policy::DevicePolicyBuilder policy_;

  scoped_ptr<DeviceSettingsProvider> provider_;

  ScopedTempDir user_data_dir_;
  FilePath original_user_data_dir_;

 private:
  DISALLOW_COPY_AND_ASSIGN(DeviceSettingsProviderTest);
};

TEST_F(DeviceSettingsProviderTest, InitializationTest) {
  owner_key_util_->SetPublicKeyFromPrivateKey(policy_.signing_key());

  // Have the service load a settings blob.
  EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());
  device_settings_service_.Load();
  device_settings_test_helper_.Flush();
  Mock::VerifyAndClearExpectations(this);

  // Verify that the policy blob has been correctly parsed and trusted.
  // The trusted flag should be set before the call to PrepareTrustedValues.
  EXPECT_EQ(CrosSettingsProvider::TRUSTED,
            provider_->PrepareTrustedValues(base::Closure()));
  const base::Value* value = provider_->Get(kStatsReportingPref);
  ASSERT_TRUE(value);
  bool bool_value;
  EXPECT_TRUE(value->GetAsBoolean(&bool_value));
  EXPECT_FALSE(bool_value);
}

TEST_F(DeviceSettingsProviderTest, InitializationTestUnowned) {
  // Have the service check the key.
  device_settings_service_.Load();
  device_settings_test_helper_.Flush();

  // The trusted flag should be set before the call to PrepareTrustedValues.
  EXPECT_EQ(CrosSettingsProvider::TRUSTED,
            provider_->PrepareTrustedValues(base::Closure()));
  const base::Value* value = provider_->Get(kReleaseChannel);
  ASSERT_TRUE(value);
  std::string string_value;
  EXPECT_TRUE(value->GetAsString(&string_value));
  EXPECT_TRUE(string_value.empty());

  // Sets should succeed though and be readable from the cache.
  EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());
  EXPECT_CALL(*this, SettingChanged(kReleaseChannel)).Times(1);
  base::StringValue new_value("stable-channel");
  provider_->Set(kReleaseChannel, new_value);
  Mock::VerifyAndClearExpectations(this);

  // This shouldn't trigger a write.
  device_settings_test_helper_.set_policy_blob(std::string());
  device_settings_test_helper_.Flush();
  EXPECT_EQ(std::string(), device_settings_test_helper_.policy_blob());

  // Verify the change has been applied.
  const base::Value* saved_value = provider_->Get(kReleaseChannel);
  ASSERT_TRUE(saved_value);
  EXPECT_TRUE(saved_value->GetAsString(&string_value));
  ASSERT_EQ("stable-channel", string_value);
}

TEST_F(DeviceSettingsProviderTest, SetPrefFailed) {
  // If we are not the owner no sets should work.
  owner_key_util_->SetPublicKeyFromPrivateKey(policy_.signing_key());

  base::FundamentalValue value(true);
  EXPECT_CALL(*this, SettingChanged(kStatsReportingPref)).Times(1);
  provider_->Set(kStatsReportingPref, value);
  Mock::VerifyAndClearExpectations(this);

  // This shouldn't trigger a write.
  device_settings_test_helper_.set_policy_blob(std::string());
  device_settings_test_helper_.Flush();
  EXPECT_EQ(std::string(), device_settings_test_helper_.policy_blob());

  // Verify the change has not been applied.
  const base::Value* saved_value = provider_->Get(kStatsReportingPref);
  ASSERT_TRUE(saved_value);
  bool bool_value;
  EXPECT_TRUE(saved_value->GetAsBoolean(&bool_value));
  EXPECT_FALSE(bool_value);
}

TEST_F(DeviceSettingsProviderTest, SetPrefSucceed) {
  owner_key_util_->SetPrivateKey(policy_.signing_key());
  device_settings_service_.SetUsername(policy_.policy_data().username());
  device_settings_test_helper_.Flush();

  base::FundamentalValue value(true);
  EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());
  EXPECT_CALL(*this, SettingChanged(kStatsReportingPref)).Times(1);
  provider_->Set(kStatsReportingPref, value);
  Mock::VerifyAndClearExpectations(this);

  // Process the store.
  device_settings_test_helper_.set_policy_blob(std::string());
  device_settings_test_helper_.Flush();

  // Verify that the device policy has been adjusted.
  ASSERT_TRUE(device_settings_service_.device_settings());
  EXPECT_TRUE(device_settings_service_.device_settings()->
                  metrics_enabled().metrics_enabled());

  // Verify the change has been applied.
  const base::Value* saved_value = provider_->Get(kStatsReportingPref);
  ASSERT_TRUE(saved_value);
  bool bool_value;
  EXPECT_TRUE(saved_value->GetAsBoolean(&bool_value));
  EXPECT_TRUE(bool_value);
}

TEST_F(DeviceSettingsProviderTest, SetPrefTwice) {
  owner_key_util_->SetPrivateKey(policy_.signing_key());
  device_settings_service_.SetUsername(policy_.policy_data().username());
  device_settings_test_helper_.Flush();

  EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());

  base::StringValue value1("beta");
  provider_->Set(kReleaseChannel, value1);
  base::StringValue value2("dev");
  provider_->Set(kReleaseChannel, value2);

  // Let the changes propagate through the system.
  device_settings_test_helper_.set_policy_blob(std::string());
  device_settings_test_helper_.Flush();

  // Verify the second change has been applied.
  const base::Value* saved_value = provider_->Get(kReleaseChannel);
  EXPECT_TRUE(value2.Equals(saved_value));

  Mock::VerifyAndClearExpectations(this);
}

TEST_F(DeviceSettingsProviderTest, PolicyRetrievalFailedBadSignature) {
  owner_key_util_->SetPublicKeyFromPrivateKey(policy_.signing_key());
  policy_.policy().set_policy_data_signature("bad signature");
  device_settings_test_helper_.set_policy_blob(policy_.GetBlob());

  device_settings_service_.Load();
  device_settings_test_helper_.Flush();

  // Verify that the cached settings blob is not "trusted".
  EXPECT_EQ(DeviceSettingsService::STORE_VALIDATION_ERROR,
            device_settings_service_.status());
  EXPECT_EQ(CrosSettingsProvider::PERMANENTLY_UNTRUSTED,
            provider_->PrepareTrustedValues(base::Closure()));
}

TEST_F(DeviceSettingsProviderTest, PolicyRetrievalNoPolicy) {
  owner_key_util_->SetPublicKeyFromPrivateKey(policy_.signing_key());
  device_settings_test_helper_.set_policy_blob(std::string());

  device_settings_service_.Load();
  device_settings_test_helper_.Flush();

  // Verify that the cached settings blob is not "trusted".
  EXPECT_EQ(DeviceSettingsService::STORE_NO_POLICY,
            device_settings_service_.status());
  EXPECT_EQ(CrosSettingsProvider::PERMANENTLY_UNTRUSTED,
            provider_->PrepareTrustedValues(base::Closure()));
}

TEST_F(DeviceSettingsProviderTest, PolicyFailedPermanentlyNotification) {
  owner_key_util_->SetPublicKeyFromPrivateKey(policy_.signing_key());
  device_settings_test_helper_.set_policy_blob(std::string());

  EXPECT_CALL(*this, GetTrustedCallback());
  EXPECT_EQ(CrosSettingsProvider::TEMPORARILY_UNTRUSTED,
            provider_->PrepareTrustedValues(
                base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback,
                           base::Unretained(this))));

  device_settings_service_.Load();
  device_settings_test_helper_.Flush();
  Mock::VerifyAndClearExpectations(this);

  EXPECT_EQ(CrosSettingsProvider::PERMANENTLY_UNTRUSTED,
            provider_->PrepareTrustedValues(base::Closure()));
}

TEST_F(DeviceSettingsProviderTest, PolicyLoadNotification) {
  EXPECT_CALL(*this, GetTrustedCallback());

  EXPECT_EQ(CrosSettingsProvider::TEMPORARILY_UNTRUSTED,
            provider_->PrepareTrustedValues(
                base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback,
                           base::Unretained(this))));

  device_settings_service_.Load();
  device_settings_test_helper_.Flush();
  Mock::VerifyAndClearExpectations(this);
}

TEST_F(DeviceSettingsProviderTest, StatsReportingMigration) {
  // Create the legacy consent file.
  FilePath consent_file =
      user_data_dir_.path().AppendASCII("Consent To Send Stats");
  ASSERT_EQ(1, file_util::WriteFile(consent_file, "0", 1));

  // This should trigger migration because the metrics policy isn't in the blob.
  device_settings_test_helper_.set_policy_blob(std::string());
  device_settings_test_helper_.Flush();
  EXPECT_EQ(std::string(), device_settings_test_helper_.policy_blob());

  // Verify that migration has kicked in.
  const base::Value* saved_value = provider_->Get(kStatsReportingPref);
  ASSERT_TRUE(saved_value);
  bool bool_value;
  EXPECT_TRUE(saved_value->GetAsBoolean(&bool_value));
  EXPECT_FALSE(bool_value);
}

} // namespace chromeos