1 // Copyright (c) 2013 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/extensions/api/declarative/declarative_rule.h" 6 7 #include "base/bind.h" 8 #include "base/message_loop/message_loop.h" 9 #include "base/test/values_test_util.h" 10 #include "base/values.h" 11 #include "extensions/common/matcher/url_matcher_constants.h" 12 #include "testing/gmock/include/gmock/gmock.h" 13 #include "testing/gtest/include/gtest/gtest.h" 14 15 namespace extensions { 16 17 using base::test::ParseJson; 18 19 namespace { 20 21 template<typename T> 22 linked_ptr<T> ScopedToLinkedPtr(scoped_ptr<T> ptr) { 23 return linked_ptr<T>(ptr.release()); 24 } 25 26 } // namespace 27 28 struct RecordingCondition { 29 typedef int MatchData; 30 31 URLMatcherConditionFactory* factory; 32 scoped_ptr<base::Value> value; 33 34 void GetURLMatcherConditionSets( 35 URLMatcherConditionSet::Vector* condition_sets) const { 36 // No condition sets. 37 } 38 39 static scoped_ptr<RecordingCondition> Create( 40 URLMatcherConditionFactory* url_matcher_condition_factory, 41 const base::Value& condition, 42 std::string* error) { 43 const base::DictionaryValue* dict = NULL; 44 if (condition.GetAsDictionary(&dict) && dict->HasKey("bad_key")) { 45 *error = "Found error key"; 46 return scoped_ptr<RecordingCondition>(); 47 } 48 49 scoped_ptr<RecordingCondition> result(new RecordingCondition()); 50 result->factory = url_matcher_condition_factory; 51 result->value.reset(condition.DeepCopy()); 52 return result.Pass(); 53 } 54 }; 55 typedef DeclarativeConditionSet<RecordingCondition> RecordingConditionSet; 56 57 TEST(DeclarativeConditionTest, ErrorConditionSet) { 58 URLMatcher matcher; 59 RecordingConditionSet::AnyVector conditions; 60 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); 61 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad_key\": 2}"))); 62 63 std::string error; 64 scoped_ptr<RecordingConditionSet> result = 65 RecordingConditionSet::Create(matcher.condition_factory(), 66 conditions, &error); 67 EXPECT_EQ("Found error key", error); 68 ASSERT_FALSE(result); 69 } 70 71 TEST(DeclarativeConditionTest, CreateConditionSet) { 72 URLMatcher matcher; 73 RecordingConditionSet::AnyVector conditions; 74 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); 75 conditions.push_back(ScopedToLinkedPtr(ParseJson("[\"val1\", 2]"))); 76 77 // Test insertion 78 std::string error; 79 scoped_ptr<RecordingConditionSet> result = 80 RecordingConditionSet::Create(matcher.condition_factory(), 81 conditions, &error); 82 EXPECT_EQ("", error); 83 ASSERT_TRUE(result); 84 EXPECT_EQ(2u, result->conditions().size()); 85 86 EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory); 87 EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals( 88 result->conditions()[0]->value.get())); 89 } 90 91 struct FulfillableCondition { 92 struct MatchData { 93 int value; 94 const std::set<URLMatcherConditionSet::ID>& url_matches; 95 }; 96 97 scoped_refptr<URLMatcherConditionSet> condition_set; 98 int condition_set_id; 99 int max_value; 100 101 URLMatcherConditionSet::ID url_matcher_condition_set_id() const { 102 return condition_set_id; 103 } 104 105 scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set() const { 106 return condition_set; 107 } 108 109 void GetURLMatcherConditionSets( 110 URLMatcherConditionSet::Vector* condition_sets) const { 111 if (condition_set.get()) 112 condition_sets->push_back(condition_set); 113 } 114 115 bool IsFulfilled(const MatchData& match_data) const { 116 if (condition_set_id != -1 && 117 !ContainsKey(match_data.url_matches, condition_set_id)) 118 return false; 119 return match_data.value <= max_value; 120 } 121 122 static scoped_ptr<FulfillableCondition> Create( 123 URLMatcherConditionFactory* /*url_matcher_condition_factory*/, 124 const base::Value& condition, 125 std::string* error) { 126 scoped_ptr<FulfillableCondition> result(new FulfillableCondition()); 127 const base::DictionaryValue* dict; 128 if (!condition.GetAsDictionary(&dict)) { 129 *error = "Expected dict"; 130 return result.Pass(); 131 } 132 if (!dict->GetInteger("url_id", &result->condition_set_id)) 133 result->condition_set_id = -1; 134 if (!dict->GetInteger("max", &result->max_value)) 135 *error = "Expected integer at ['max']"; 136 if (result->condition_set_id != -1) { 137 result->condition_set = new URLMatcherConditionSet( 138 result->condition_set_id, 139 URLMatcherConditionSet::Conditions()); 140 } 141 return result.Pass(); 142 } 143 }; 144 145 TEST(DeclarativeConditionTest, FulfillConditionSet) { 146 typedef DeclarativeConditionSet<FulfillableCondition> FulfillableConditionSet; 147 FulfillableConditionSet::AnyVector conditions; 148 conditions.push_back(ScopedToLinkedPtr(ParseJson( 149 "{\"url_id\": 1, \"max\": 3}"))); 150 conditions.push_back(ScopedToLinkedPtr(ParseJson( 151 "{\"url_id\": 2, \"max\": 5}"))); 152 conditions.push_back(ScopedToLinkedPtr(ParseJson( 153 "{\"url_id\": 3, \"max\": 1}"))); 154 conditions.push_back(ScopedToLinkedPtr(ParseJson( 155 "{\"max\": -5}"))); // No url. 156 157 // Test insertion 158 std::string error; 159 scoped_ptr<FulfillableConditionSet> result = 160 FulfillableConditionSet::Create(NULL, conditions, &error); 161 ASSERT_EQ("", error); 162 ASSERT_TRUE(result); 163 EXPECT_EQ(4u, result->conditions().size()); 164 165 std::set<URLMatcherConditionSet::ID> url_matches; 166 FulfillableCondition::MatchData match_data = { 0, url_matches }; 167 EXPECT_FALSE(result->IsFulfilled(1, match_data)) 168 << "Testing an ID that's not in url_matches forwards to the Condition, " 169 << "which doesn't match."; 170 EXPECT_FALSE(result->IsFulfilled(-1, match_data)) 171 << "Testing the 'no ID' value tries to match the 4th condition, but " 172 << "its max is too low."; 173 match_data.value = -5; 174 EXPECT_TRUE(result->IsFulfilled(-1, match_data)) 175 << "Testing the 'no ID' value tries to match the 4th condition, and " 176 << "this value is low enough."; 177 178 url_matches.insert(1); 179 match_data.value = 3; 180 EXPECT_TRUE(result->IsFulfilled(1, match_data)) 181 << "Tests a condition with a url matcher, for a matching value."; 182 match_data.value = 4; 183 EXPECT_FALSE(result->IsFulfilled(1, match_data)) 184 << "Tests a condition with a url matcher, for a non-matching value " 185 << "that would match a different condition."; 186 url_matches.insert(2); 187 EXPECT_TRUE(result->IsFulfilled(2, match_data)) 188 << "Tests with 2 elements in the match set."; 189 190 // Check the condition sets: 191 URLMatcherConditionSet::Vector condition_sets; 192 result->GetURLMatcherConditionSets(&condition_sets); 193 ASSERT_EQ(3U, condition_sets.size()); 194 EXPECT_EQ(1, condition_sets[0]->id()); 195 EXPECT_EQ(2, condition_sets[1]->id()); 196 EXPECT_EQ(3, condition_sets[2]->id()); 197 } 198 199 // DeclarativeAction 200 201 class SummingAction : public base::RefCounted<SummingAction> { 202 public: 203 typedef int ApplyInfo; 204 205 SummingAction(int increment, int min_priority) 206 : increment_(increment), min_priority_(min_priority) {} 207 208 static scoped_refptr<const SummingAction> Create(const base::Value& action, 209 std::string* error, 210 bool* bad_message) { 211 int increment = 0; 212 int min_priority = 0; 213 const base::DictionaryValue* dict = NULL; 214 EXPECT_TRUE(action.GetAsDictionary(&dict)); 215 if (dict->HasKey("error")) { 216 EXPECT_TRUE(dict->GetString("error", error)); 217 return scoped_refptr<const SummingAction>(NULL); 218 } 219 if (dict->HasKey("bad")) { 220 *bad_message = true; 221 return scoped_refptr<const SummingAction>(NULL); 222 } 223 224 EXPECT_TRUE(dict->GetInteger("value", &increment)); 225 dict->GetInteger("priority", &min_priority); 226 return scoped_refptr<const SummingAction>( 227 new SummingAction(increment, min_priority)); 228 } 229 230 void Apply(const std::string& extension_id, 231 const base::Time& install_time, 232 int* sum) const { 233 *sum += increment_; 234 } 235 236 int increment() const { return increment_; } 237 int minimum_priority() const { 238 return min_priority_; 239 } 240 241 private: 242 friend class base::RefCounted<SummingAction>; 243 virtual ~SummingAction() {} 244 245 int increment_; 246 int min_priority_; 247 }; 248 typedef DeclarativeActionSet<SummingAction> SummingActionSet; 249 250 TEST(DeclarativeActionTest, ErrorActionSet) { 251 SummingActionSet::AnyVector actions; 252 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); 253 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"error\": \"the error\"}"))); 254 255 std::string error; 256 bool bad = false; 257 scoped_ptr<SummingActionSet> result = 258 SummingActionSet::Create(actions, &error, &bad); 259 EXPECT_EQ("the error", error); 260 EXPECT_FALSE(bad); 261 EXPECT_FALSE(result); 262 263 actions.clear(); 264 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); 265 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad\": 3}"))); 266 result = SummingActionSet::Create(actions, &error, &bad); 267 EXPECT_EQ("", error); 268 EXPECT_TRUE(bad); 269 EXPECT_FALSE(result); 270 } 271 272 TEST(DeclarativeActionTest, ApplyActionSet) { 273 SummingActionSet::AnyVector actions; 274 actions.push_back(ScopedToLinkedPtr(ParseJson( 275 "{\"value\": 1," 276 " \"priority\": 5}"))); 277 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 2}"))); 278 279 // Test insertion 280 std::string error; 281 bool bad = false; 282 scoped_ptr<SummingActionSet> result = 283 SummingActionSet::Create(actions, &error, &bad); 284 EXPECT_EQ("", error); 285 EXPECT_FALSE(bad); 286 ASSERT_TRUE(result); 287 EXPECT_EQ(2u, result->actions().size()); 288 289 int sum = 0; 290 result->Apply("ext_id", base::Time(), &sum); 291 EXPECT_EQ(3, sum); 292 EXPECT_EQ(5, result->GetMinimumPriority()); 293 } 294 295 TEST(DeclarativeRuleTest, Create) { 296 typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule; 297 linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule); 298 ASSERT_TRUE(Rule::JsonRule::Populate( 299 *ParseJson("{ \n" 300 " \"id\": \"rule1\", \n" 301 " \"conditions\": [ \n" 302 " {\"url_id\": 1, \"max\": 3}, \n" 303 " {\"url_id\": 2, \"max\": 5}, \n" 304 " ], \n" 305 " \"actions\": [ \n" 306 " { \n" 307 " \"value\": 2 \n" 308 " } \n" 309 " ], \n" 310 " \"priority\": 200 \n" 311 "}"), 312 json_rule.get())); 313 314 const char kExtensionId[] = "ext1"; 315 316 base::Time install_time = base::Time::Now(); 317 318 URLMatcher matcher; 319 std::string error; 320 scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(), 321 kExtensionId, 322 install_time, 323 json_rule, 324 Rule::ConsistencyChecker(), 325 &error)); 326 EXPECT_EQ("", error); 327 ASSERT_TRUE(rule.get()); 328 329 EXPECT_EQ(kExtensionId, rule->id().first); 330 EXPECT_EQ("rule1", rule->id().second); 331 332 EXPECT_EQ(200, rule->priority()); 333 334 const Rule::ConditionSet& condition_set = rule->conditions(); 335 const Rule::ConditionSet::Conditions& conditions = 336 condition_set.conditions(); 337 ASSERT_EQ(2u, conditions.size()); 338 EXPECT_EQ(3, conditions[0]->max_value); 339 EXPECT_EQ(5, conditions[1]->max_value); 340 341 const Rule::ActionSet& action_set = rule->actions(); 342 const Rule::ActionSet::Actions& actions = action_set.actions(); 343 ASSERT_EQ(1u, actions.size()); 344 EXPECT_EQ(2, actions[0]->increment()); 345 346 int sum = 0; 347 rule->Apply(&sum); 348 EXPECT_EQ(2, sum); 349 } 350 351 bool AtLeastOneCondition( 352 const DeclarativeConditionSet<FulfillableCondition>* conditions, 353 const DeclarativeActionSet<SummingAction>* actions, 354 std::string* error) { 355 if (conditions->conditions().empty()) { 356 *error = "No conditions"; 357 return false; 358 } 359 return true; 360 } 361 362 TEST(DeclarativeRuleTest, CheckConsistency) { 363 typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule; 364 URLMatcher matcher; 365 std::string error; 366 linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule); 367 const char kExtensionId[] = "ext1"; 368 369 ASSERT_TRUE(Rule::JsonRule::Populate( 370 *ParseJson("{ \n" 371 " \"id\": \"rule1\", \n" 372 " \"conditions\": [ \n" 373 " {\"url_id\": 1, \"max\": 3}, \n" 374 " {\"url_id\": 2, \"max\": 5}, \n" 375 " ], \n" 376 " \"actions\": [ \n" 377 " { \n" 378 " \"value\": 2 \n" 379 " } \n" 380 " ], \n" 381 " \"priority\": 200 \n" 382 "}"), 383 json_rule.get())); 384 scoped_ptr<Rule> rule( 385 Rule::Create(matcher.condition_factory(), kExtensionId, base::Time(), 386 json_rule, base::Bind(AtLeastOneCondition), &error)); 387 EXPECT_TRUE(rule); 388 EXPECT_EQ("", error); 389 390 ASSERT_TRUE(Rule::JsonRule::Populate( 391 *ParseJson("{ \n" 392 " \"id\": \"rule1\", \n" 393 " \"conditions\": [ \n" 394 " ], \n" 395 " \"actions\": [ \n" 396 " { \n" 397 " \"value\": 2 \n" 398 " } \n" 399 " ], \n" 400 " \"priority\": 200 \n" 401 "}"), 402 json_rule.get())); 403 rule = Rule::Create(matcher.condition_factory(), kExtensionId, base::Time(), 404 json_rule, base::Bind(AtLeastOneCondition), &error); 405 EXPECT_FALSE(rule); 406 EXPECT_EQ("No conditions", error); 407 } 408 409 } // namespace extensions 410