1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/safe_browsing/incident_reporting/preference_validation_delegate.h"
6
7#include <string>
8#include <vector>
9
10#include "base/bind.h"
11#include "base/compiler_specific.h"
12#include "base/memory/scoped_vector.h"
13#include "base/values.h"
14#include "chrome/common/safe_browsing/csd.pb.h"
15#include "testing/gtest/include/gtest/gtest.h"
16
17// A basic test harness that creates a delegate instance for which it stores all
18// incidents. Tests can push data to the delegate and verify that the test
19// instance was provided with the expected data.
20class PreferenceValidationDelegateTest : public testing::Test {
21 protected:
22  typedef ScopedVector<safe_browsing::ClientIncidentReport_IncidentData>
23      IncidentVector;
24
25  PreferenceValidationDelegateTest()
26      : kPrefPath_("atomic.pref"),
27        null_value_(base::Value::CreateNullValue()) {}
28
29  virtual void SetUp() OVERRIDE {
30    testing::Test::SetUp();
31    invalid_keys_.push_back(std::string("one"));
32    invalid_keys_.push_back(std::string("two"));
33    instance_.reset(new safe_browsing::PreferenceValidationDelegate(
34        base::Bind(&PreferenceValidationDelegateTest::AddIncident,
35                   base::Unretained(this))));
36  }
37
38  void AddIncident(
39      scoped_ptr<safe_browsing::ClientIncidentReport_IncidentData> data) {
40    incidents_.push_back(data.release());
41  }
42
43  static void ExpectValueStatesEquate(
44      PrefHashStoreTransaction::ValueState store_state,
45      safe_browsing::
46          ClientIncidentReport_IncidentData_TrackedPreferenceIncident_ValueState
47              incident_state) {
48    typedef safe_browsing::
49        ClientIncidentReport_IncidentData_TrackedPreferenceIncident TPIncident;
50    switch (store_state) {
51      case PrefHashStoreTransaction::CLEARED:
52        EXPECT_EQ(TPIncident::CLEARED, incident_state);
53        break;
54      case PrefHashStoreTransaction::CHANGED:
55        EXPECT_EQ(TPIncident::CHANGED, incident_state);
56        break;
57      case PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE:
58        EXPECT_EQ(TPIncident::UNTRUSTED_UNKNOWN_VALUE, incident_state);
59        break;
60      default:
61        FAIL() << "unexpected store state";
62        break;
63    }
64  }
65
66  static void ExpectKeysEquate(
67      const std::vector<std::string>& store_keys,
68      const google::protobuf::RepeatedPtrField<std::string>& incident_keys) {
69    ASSERT_EQ(store_keys.size(), static_cast<size_t>(incident_keys.size()));
70    for (int i = 0; i < incident_keys.size(); ++i) {
71      EXPECT_EQ(store_keys[i], incident_keys.Get(i));
72    }
73  }
74
75  const std::string kPrefPath_;
76  IncidentVector incidents_;
77  scoped_ptr<base::Value> null_value_;
78  base::DictionaryValue dict_value_;
79  std::vector<std::string> invalid_keys_;
80  scoped_ptr<TrackedPreferenceValidationDelegate> instance_;
81};
82
83// Tests that a NULL value results in an incident with no value.
84TEST_F(PreferenceValidationDelegateTest, NullValue) {
85  instance_->OnAtomicPreferenceValidation(kPrefPath_,
86                                          NULL,
87                                          PrefHashStoreTransaction::CLEARED,
88                                          TrackedPreferenceHelper::DONT_RESET);
89  safe_browsing::ClientIncidentReport_IncidentData* incident =
90      incidents_.back();
91  EXPECT_FALSE(incident->tracked_preference().has_atomic_value());
92  EXPECT_EQ(
93      safe_browsing::
94          ClientIncidentReport_IncidentData_TrackedPreferenceIncident::CLEARED,
95      incident->tracked_preference().value_state());
96}
97
98// Tests that all supported value types can be stringified into an incident. The
99// parameters for the test are the type of value to test and the expected value
100// string.
101class PreferenceValidationDelegateValues
102    : public PreferenceValidationDelegateTest,
103      public testing::WithParamInterface<
104          std::tr1::tuple<base::Value::Type, const char*> > {
105 protected:
106  virtual void SetUp() OVERRIDE {
107    PreferenceValidationDelegateTest::SetUp();
108    value_type_ = std::tr1::get<0>(GetParam());
109    expected_value_ = std::tr1::get<1>(GetParam());
110  }
111
112  static scoped_ptr<base::Value> MakeValue(base::Value::Type value_type) {
113    using base::Value;
114    switch (value_type) {
115      case Value::TYPE_NULL:
116        return make_scoped_ptr(Value::CreateNullValue());
117      case Value::TYPE_BOOLEAN:
118        return scoped_ptr<Value>(new base::FundamentalValue(false));
119      case Value::TYPE_INTEGER:
120        return scoped_ptr<Value>(new base::FundamentalValue(47));
121      case Value::TYPE_DOUBLE:
122        return scoped_ptr<Value>(new base::FundamentalValue(0.47));
123      case Value::TYPE_STRING:
124        return scoped_ptr<Value>(new base::StringValue("i have a spleen"));
125      case Value::TYPE_DICTIONARY: {
126        scoped_ptr<base::DictionaryValue> value(new base::DictionaryValue());
127        value->SetInteger("twenty-two", 22);
128        value->SetInteger("forty-seven", 47);
129        return value.PassAs<Value>();
130      }
131      case Value::TYPE_LIST: {
132        scoped_ptr<base::ListValue> value(new base::ListValue());
133        value->AppendInteger(22);
134        value->AppendInteger(47);
135        return value.PassAs<Value>();
136      }
137      default:
138        ADD_FAILURE() << "unsupported value type " << value_type;
139    }
140    return scoped_ptr<Value>();
141  }
142
143  base::Value::Type value_type_;
144  const char* expected_value_;
145};
146
147TEST_P(PreferenceValidationDelegateValues, Value) {
148  instance_->OnAtomicPreferenceValidation(kPrefPath_,
149                                          MakeValue(value_type_).get(),
150                                          PrefHashStoreTransaction::CLEARED,
151                                          TrackedPreferenceHelper::DONT_RESET);
152  ASSERT_EQ(1U, incidents_.size());
153  safe_browsing::ClientIncidentReport_IncidentData* incident =
154      incidents_.back();
155  EXPECT_EQ(std::string(expected_value_),
156            incident->tracked_preference().atomic_value());
157}
158
159INSTANTIATE_TEST_CASE_P(
160    Values,
161    PreferenceValidationDelegateValues,
162    // On Android, make_tuple(..., "null") doesn't compile due to the error:
163    // testing/gtest/include/gtest/internal/gtest-tuple.h:246:48:
164    //   error: array used as initializer
165    testing::Values(
166        std::tr1::make_tuple(base::Value::TYPE_NULL,
167                             const_cast<char*>("null")),
168        std::tr1::make_tuple(base::Value::TYPE_BOOLEAN,
169                             const_cast<char*>("false")),
170        std::tr1::make_tuple(base::Value::TYPE_INTEGER,
171                             const_cast<char*>("47")),
172        std::tr1::make_tuple(base::Value::TYPE_DOUBLE,
173                             const_cast<char*>("0.47")),
174        std::tr1::make_tuple(base::Value::TYPE_STRING,
175                             const_cast<char*>("i have a spleen")),
176        std::tr1::make_tuple(base::Value::TYPE_DICTIONARY,
177            const_cast<char*>("{\"forty-seven\":47,\"twenty-two\":22}")),
178        std::tr1::make_tuple(base::Value::TYPE_LIST,
179                             const_cast<char*>("[22,47]"))));
180
181// Tests that no incidents are reported for relevant combinations of ValueState.
182class PreferenceValidationDelegateNoIncident
183    : public PreferenceValidationDelegateTest,
184      public testing::WithParamInterface<PrefHashStoreTransaction::ValueState> {
185 protected:
186  virtual void SetUp() OVERRIDE {
187    PreferenceValidationDelegateTest::SetUp();
188    value_state_ = GetParam();
189  }
190
191  PrefHashStoreTransaction::ValueState value_state_;
192};
193
194TEST_P(PreferenceValidationDelegateNoIncident, Atomic) {
195  instance_->OnAtomicPreferenceValidation(kPrefPath_,
196                                          null_value_.get(),
197                                          value_state_,
198                                          TrackedPreferenceHelper::DONT_RESET);
199  EXPECT_EQ(0U, incidents_.size());
200}
201
202TEST_P(PreferenceValidationDelegateNoIncident, Split) {
203  instance_->OnSplitPreferenceValidation(kPrefPath_,
204                                         &dict_value_,
205                                         invalid_keys_,
206                                         value_state_,
207                                         TrackedPreferenceHelper::DONT_RESET);
208  EXPECT_EQ(0U, incidents_.size());
209}
210
211INSTANTIATE_TEST_CASE_P(
212    NoIncident,
213    PreferenceValidationDelegateNoIncident,
214    testing::Values(PrefHashStoreTransaction::UNCHANGED,
215                    PrefHashStoreTransaction::SECURE_LEGACY,
216                    PrefHashStoreTransaction::TRUSTED_UNKNOWN_VALUE));
217
218// Tests that incidents are reported for relevant combinations of ValueState and
219// ResetAction.
220class PreferenceValidationDelegateWithIncident
221    : public PreferenceValidationDelegateTest,
222      public testing::WithParamInterface<
223          std::tr1::tuple<PrefHashStoreTransaction::ValueState,
224                          TrackedPreferenceHelper::ResetAction> > {
225 protected:
226  virtual void SetUp() OVERRIDE {
227    PreferenceValidationDelegateTest::SetUp();
228    value_state_ = std::tr1::get<0>(GetParam());
229    reset_action_ = std::tr1::get<1>(GetParam());
230  }
231
232  PrefHashStoreTransaction::ValueState value_state_;
233  TrackedPreferenceHelper::ResetAction reset_action_;
234};
235
236TEST_P(PreferenceValidationDelegateWithIncident, Atomic) {
237  instance_->OnAtomicPreferenceValidation(
238      kPrefPath_, null_value_.get(), value_state_, reset_action_);
239  ASSERT_EQ(1U, incidents_.size());
240  safe_browsing::ClientIncidentReport_IncidentData* incident =
241      incidents_.back();
242  EXPECT_TRUE(incident->has_tracked_preference());
243  const safe_browsing::
244      ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident =
245          incident->tracked_preference();
246  EXPECT_EQ(kPrefPath_, tp_incident.path());
247  EXPECT_EQ(0, tp_incident.split_key_size());
248  EXPECT_TRUE(tp_incident.has_atomic_value());
249  EXPECT_EQ(std::string("null"), tp_incident.atomic_value());
250  EXPECT_TRUE(tp_incident.has_value_state());
251  ExpectValueStatesEquate(value_state_, tp_incident.value_state());
252}
253
254TEST_P(PreferenceValidationDelegateWithIncident, Split) {
255  instance_->OnSplitPreferenceValidation(
256      kPrefPath_, &dict_value_, invalid_keys_, value_state_, reset_action_);
257  ASSERT_EQ(1U, incidents_.size());
258  safe_browsing::ClientIncidentReport_IncidentData* incident =
259      incidents_.back();
260  EXPECT_TRUE(incident->has_tracked_preference());
261  const safe_browsing::
262      ClientIncidentReport_IncidentData_TrackedPreferenceIncident& tp_incident =
263          incident->tracked_preference();
264  EXPECT_EQ(kPrefPath_, tp_incident.path());
265  EXPECT_FALSE(tp_incident.has_atomic_value());
266  ExpectKeysEquate(invalid_keys_, tp_incident.split_key());
267  EXPECT_TRUE(tp_incident.has_value_state());
268  ExpectValueStatesEquate(value_state_, tp_incident.value_state());
269}
270
271INSTANTIATE_TEST_CASE_P(
272    WithIncident,
273    PreferenceValidationDelegateWithIncident,
274    testing::Combine(
275        testing::Values(PrefHashStoreTransaction::CLEARED,
276                        PrefHashStoreTransaction::CHANGED,
277                        PrefHashStoreTransaction::UNTRUSTED_UNKNOWN_VALUE),
278        testing::Values(TrackedPreferenceHelper::WANTED_RESET,
279                        TrackedPreferenceHelper::DO_RESET)));
280