// Copyright 2014 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/safe_browsing/incident_reporting/preference_validation_delegate.h" #include #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/memory/scoped_vector.h" #include "base/values.h" #include "chrome/browser/safe_browsing/incident_reporting/incident.h" #include "chrome/browser/safe_browsing/incident_reporting/mock_incident_receiver.h" #include "chrome/common/safe_browsing/csd.pb.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; using ::testing::IsNull; using ::testing::NiceMock; using ::testing::WithArg; // A basic test harness that creates a delegate instance for which it stores all // incidents. Tests can push data to the delegate and verify that the test // instance was provided with the expected data. class PreferenceValidationDelegateTest : public testing::Test { protected: typedef ScopedVector IncidentVector; PreferenceValidationDelegateTest() : kPrefPath_("atomic.pref"), null_value_(base::Value::CreateNullValue()) {} void SetUp() override { testing::Test::SetUp(); invalid_keys_.push_back(std::string("one")); invalid_keys_.push_back(std::string("two")); scoped_ptr receiver( new NiceMock()); ON_CALL(*receiver, DoAddIncidentForProfile(IsNull(), _)) .WillByDefault(WithArg<1>(TakeIncidentToVector(&incidents_))); instance_.reset(new safe_browsing::PreferenceValidationDelegate( nullptr, receiver.Pass())); } static void ExpectValueStatesEquate( PrefHashStoreTransaction::ValueState store_state, safe_browsing:: ClientIncidentReport_IncidentData_TrackedPreferenceIncident_ValueState incident_state) { typedef safe_browsing:: ClientIncidentReport_IncidentData_TrackedPreferenceIncident TPIncident; switch (store_state) { case PrefHashStoreTransaction::CLEARED: EXPECT_EQ(TPIncident::CLEARED, incident_state); break; case PrefHashStoreTransaction::CHANGED: EXPECT_EQ(TPIncident::CHANGED, incident_state); break; case PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE: EXPECT_EQ(TPIncident::UNTRUSTED_UNKNOWN_VALUE, incident_state); break; default: FAIL() << "unexpected store state"; break; } } static void ExpectKeysEquate( const std::vector& store_keys, const google::protobuf::RepeatedPtrField& incident_keys) { ASSERT_EQ(store_keys.size(), static_cast(incident_keys.size())); for (int i = 0; i < incident_keys.size(); ++i) { EXPECT_EQ(store_keys[i], incident_keys.Get(i)); } } const std::string kPrefPath_; IncidentVector incidents_; scoped_ptr null_value_; base::DictionaryValue dict_value_; std::vector invalid_keys_; scoped_ptr instance_; }; // Tests that a NULL value results in an incident with no value. TEST_F(PreferenceValidationDelegateTest, NullValue) { instance_->OnAtomicPreferenceValidation(kPrefPath_, NULL, PrefHashStoreTransaction::CLEARED, false /* is_personal */); scoped_ptr incident( incidents_.back()->TakePayload()); EXPECT_FALSE(incident->tracked_preference().has_atomic_value()); EXPECT_EQ( safe_browsing:: ClientIncidentReport_IncidentData_TrackedPreferenceIncident::CLEARED, incident->tracked_preference().value_state()); } // Tests that all supported value types can be stringified into an incident. The // parameters for the test are the type of value to test and the expected value // string. class PreferenceValidationDelegateValues : public PreferenceValidationDelegateTest, public testing::WithParamInterface< std::tr1::tuple > { protected: void SetUp() override { PreferenceValidationDelegateTest::SetUp(); value_type_ = std::tr1::get<0>(GetParam()); expected_value_ = std::tr1::get<1>(GetParam()); } static scoped_ptr MakeValue(base::Value::Type value_type) { using base::Value; switch (value_type) { case Value::TYPE_NULL: return Value::CreateNullValue(); case Value::TYPE_BOOLEAN: return scoped_ptr(new base::FundamentalValue(false)); case Value::TYPE_INTEGER: return scoped_ptr(new base::FundamentalValue(47)); case Value::TYPE_DOUBLE: return scoped_ptr(new base::FundamentalValue(0.47)); case Value::TYPE_STRING: return scoped_ptr(new base::StringValue("i have a spleen")); case Value::TYPE_DICTIONARY: { scoped_ptr value(new base::DictionaryValue()); value->SetInteger("twenty-two", 22); value->SetInteger("forty-seven", 47); return value.Pass(); } case Value::TYPE_LIST: { scoped_ptr value(new base::ListValue()); value->AppendInteger(22); value->AppendInteger(47); return value.Pass(); } default: ADD_FAILURE() << "unsupported value type " << value_type; } return scoped_ptr(); } base::Value::Type value_type_; const char* expected_value_; }; TEST_P(PreferenceValidationDelegateValues, Value) { instance_->OnAtomicPreferenceValidation(kPrefPath_, MakeValue(value_type_).get(), PrefHashStoreTransaction::CLEARED, false /* is_personal */); ASSERT_EQ(1U, incidents_.size()); scoped_ptr incident( incidents_.back()->TakePayload()); EXPECT_EQ(std::string(expected_value_), incident->tracked_preference().atomic_value()); } INSTANTIATE_TEST_CASE_P( Values, PreferenceValidationDelegateValues, // On Android, make_tuple(..., "null") doesn't compile due to the error: // testing/gtest/include/gtest/internal/gtest-tuple.h:246:48: // error: array used as initializer testing::Values( std::tr1::make_tuple(base::Value::TYPE_NULL, const_cast("null")), std::tr1::make_tuple(base::Value::TYPE_BOOLEAN, const_cast("false")), std::tr1::make_tuple(base::Value::TYPE_INTEGER, const_cast("47")), std::tr1::make_tuple(base::Value::TYPE_DOUBLE, const_cast("0.47")), std::tr1::make_tuple(base::Value::TYPE_STRING, const_cast("i have a spleen")), std::tr1::make_tuple(base::Value::TYPE_DICTIONARY, const_cast("{\"forty-seven\":47,\"twenty-two\":22}")), std::tr1::make_tuple(base::Value::TYPE_LIST, const_cast("[22,47]")))); // Tests that no incidents are reported for relevant combinations of ValueState. class PreferenceValidationDelegateNoIncident : public PreferenceValidationDelegateTest, public testing::WithParamInterface { protected: void SetUp() override { PreferenceValidationDelegateTest::SetUp(); value_state_ = GetParam(); } PrefHashStoreTransaction::ValueState value_state_; }; TEST_P(PreferenceValidationDelegateNoIncident, Atomic) { instance_->OnAtomicPreferenceValidation(kPrefPath_, null_value_.get(), value_state_, false /* is_personal */); EXPECT_EQ(0U, incidents_.size()); } TEST_P(PreferenceValidationDelegateNoIncident, Split) { instance_->OnSplitPreferenceValidation(kPrefPath_, &dict_value_, invalid_keys_, value_state_, false /* is_personal */); EXPECT_EQ(0U, incidents_.size()); } INSTANTIATE_TEST_CASE_P( NoIncident, PreferenceValidationDelegateNoIncident, testing::Values(PrefHashStoreTransaction::UNCHANGED, PrefHashStoreTransaction::SECURE_LEGACY, PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE)); // Tests that incidents are reported for relevant combinations of ValueState and // impersonal/personal. class PreferenceValidationDelegateWithIncident : public PreferenceValidationDelegateTest, public testing::WithParamInterface< std::tr1::tuple> { protected: void SetUp() override { PreferenceValidationDelegateTest::SetUp(); value_state_ = std::tr1::get<0>(GetParam()); is_personal_ = std::tr1::get<1>(GetParam()); } PrefHashStoreTransaction::ValueState value_state_; bool is_personal_; }; TEST_P(PreferenceValidationDelegateWithIncident, Atomic) { instance_->OnAtomicPreferenceValidation( kPrefPath_, null_value_.get(), value_state_, is_personal_); ASSERT_EQ(1U, incidents_.size()); scoped_ptr incident( incidents_.back()->TakePayload()); EXPECT_TRUE(incident->has_tracked_preference()); const safe_browsing:: ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident = incident->tracked_preference(); EXPECT_EQ(kPrefPath_, tp_incident.path()); EXPECT_EQ(0, tp_incident.split_key_size()); if (!is_personal_) { EXPECT_TRUE(tp_incident.has_atomic_value()); EXPECT_EQ(std::string("null"), tp_incident.atomic_value()); } else { EXPECT_FALSE(tp_incident.has_atomic_value()); } EXPECT_TRUE(tp_incident.has_value_state()); ExpectValueStatesEquate(value_state_, tp_incident.value_state()); } TEST_P(PreferenceValidationDelegateWithIncident, Split) { instance_->OnSplitPreferenceValidation( kPrefPath_, &dict_value_, invalid_keys_, value_state_, is_personal_); ASSERT_EQ(1U, incidents_.size()); scoped_ptr incident( incidents_.back()->TakePayload()); EXPECT_TRUE(incident->has_tracked_preference()); const safe_browsing:: ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident = incident->tracked_preference(); EXPECT_EQ(kPrefPath_, tp_incident.path()); EXPECT_FALSE(tp_incident.has_atomic_value()); if (!is_personal_) ExpectKeysEquate(invalid_keys_, tp_incident.split_key()); else EXPECT_EQ(0, tp_incident.split_key_size()); EXPECT_TRUE(tp_incident.has_value_state()); ExpectValueStatesEquate(value_state_, tp_incident.value_state()); } INSTANTIATE_TEST_CASE_P( WithIncident, PreferenceValidationDelegateWithIncident, testing::Combine( testing::Values(PrefHashStoreTransaction::CLEARED, PrefHashStoreTransaction::CHANGED, PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE), testing::Bool()));