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.
5 #include "chrome/browser/extensions/api/declarative/declarative_rule.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/test/values_test_util.h"
10 #include "base/values.h"
11 #include "chrome/common/extensions/extension_builder.h"
12 #include "extensions/common/matcher/url_matcher_constants.h"
13 #include "testing/gmock/include/gmock/gmock.h"
14 #include "testing/gtest/include/gtest/gtest.h"
16 namespace extensions {
18 using base::test::ParseJson;
23 linked_ptr<T> ScopedToLinkedPtr(scoped_ptr<T> ptr) {
24 return linked_ptr<T>(ptr.release());
27 scoped_ptr<DictionaryValue> SimpleManifest() {
28 return DictionaryBuilder()
29 .Set("name", "extension")
30 .Set("manifest_version", 2)
31 .Set("version", "1.0")
37 struct RecordingCondition {
38 typedef int MatchData;
40 URLMatcherConditionFactory* factory;
41 scoped_ptr<base::Value> value;
43 void GetURLMatcherConditionSets(
44 URLMatcherConditionSet::Vector* condition_sets) const {
48 static scoped_ptr<RecordingCondition> Create(
49 const Extension* extension,
50 URLMatcherConditionFactory* url_matcher_condition_factory,
51 const base::Value& condition,
53 const base::DictionaryValue* dict = NULL;
54 if (condition.GetAsDictionary(&dict) && dict->HasKey("bad_key")) {
55 *error = "Found error key";
56 return scoped_ptr<RecordingCondition>();
59 scoped_ptr<RecordingCondition> result(new RecordingCondition());
60 result->factory = url_matcher_condition_factory;
61 result->value.reset(condition.DeepCopy());
65 typedef DeclarativeConditionSet<RecordingCondition> RecordingConditionSet;
67 TEST(DeclarativeConditionTest, ErrorConditionSet) {
69 RecordingConditionSet::AnyVector conditions;
70 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}")));
71 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad_key\": 2}")));
74 scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create(
75 NULL, matcher.condition_factory(), conditions, &error);
76 EXPECT_EQ("Found error key", error);
80 TEST(DeclarativeConditionTest, CreateConditionSet) {
82 RecordingConditionSet::AnyVector conditions;
83 conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}")));
84 conditions.push_back(ScopedToLinkedPtr(ParseJson("[\"val1\", 2]")));
88 scoped_ptr<RecordingConditionSet> result = RecordingConditionSet::Create(
89 NULL, matcher.condition_factory(), conditions, &error);
92 EXPECT_EQ(2u, result->conditions().size());
94 EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory);
95 EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals(
96 result->conditions()[0]->value.get()));
99 struct FulfillableCondition {
102 const std::set<URLMatcherConditionSet::ID>& url_matches;
105 scoped_refptr<URLMatcherConditionSet> condition_set;
106 int condition_set_id;
109 URLMatcherConditionSet::ID url_matcher_condition_set_id() const {
110 return condition_set_id;
113 scoped_refptr<URLMatcherConditionSet> url_matcher_condition_set() const {
114 return condition_set;
117 void GetURLMatcherConditionSets(
118 URLMatcherConditionSet::Vector* condition_sets) const {
119 if (condition_set.get())
120 condition_sets->push_back(condition_set);
123 bool IsFulfilled(const MatchData& match_data) const {
124 if (condition_set_id != -1 &&
125 !ContainsKey(match_data.url_matches, condition_set_id))
127 return match_data.value <= max_value;
130 static scoped_ptr<FulfillableCondition> Create(
131 const Extension* extension,
132 URLMatcherConditionFactory* url_matcher_condition_factory,
133 const base::Value& condition,
134 std::string* error) {
135 scoped_ptr<FulfillableCondition> result(new FulfillableCondition());
136 const base::DictionaryValue* dict;
137 if (!condition.GetAsDictionary(&dict)) {
138 *error = "Expected dict";
139 return result.Pass();
141 if (!dict->GetInteger("url_id", &result->condition_set_id))
142 result->condition_set_id = -1;
143 if (!dict->GetInteger("max", &result->max_value))
144 *error = "Expected integer at ['max']";
145 if (result->condition_set_id != -1) {
146 result->condition_set = new URLMatcherConditionSet(
147 result->condition_set_id,
148 URLMatcherConditionSet::Conditions());
150 return result.Pass();
154 TEST(DeclarativeConditionTest, FulfillConditionSet) {
155 typedef DeclarativeConditionSet<FulfillableCondition> FulfillableConditionSet;
156 FulfillableConditionSet::AnyVector conditions;
157 conditions.push_back(ScopedToLinkedPtr(ParseJson(
158 "{\"url_id\": 1, \"max\": 3}")));
159 conditions.push_back(ScopedToLinkedPtr(ParseJson(
160 "{\"url_id\": 2, \"max\": 5}")));
161 conditions.push_back(ScopedToLinkedPtr(ParseJson(
162 "{\"url_id\": 3, \"max\": 1}")));
163 conditions.push_back(ScopedToLinkedPtr(ParseJson(
164 "{\"max\": -5}"))); // No url.
168 scoped_ptr<FulfillableConditionSet> result =
169 FulfillableConditionSet::Create(NULL, NULL, conditions, &error);
170 ASSERT_EQ("", error);
172 EXPECT_EQ(4u, result->conditions().size());
174 std::set<URLMatcherConditionSet::ID> url_matches;
175 FulfillableCondition::MatchData match_data = { 0, url_matches };
176 EXPECT_FALSE(result->IsFulfilled(1, match_data))
177 << "Testing an ID that's not in url_matches forwards to the Condition, "
178 << "which doesn't match.";
179 EXPECT_FALSE(result->IsFulfilled(-1, match_data))
180 << "Testing the 'no ID' value tries to match the 4th condition, but "
181 << "its max is too low.";
182 match_data.value = -5;
183 EXPECT_TRUE(result->IsFulfilled(-1, match_data))
184 << "Testing the 'no ID' value tries to match the 4th condition, and "
185 << "this value is low enough.";
187 url_matches.insert(1);
188 match_data.value = 3;
189 EXPECT_TRUE(result->IsFulfilled(1, match_data))
190 << "Tests a condition with a url matcher, for a matching value.";
191 match_data.value = 4;
192 EXPECT_FALSE(result->IsFulfilled(1, match_data))
193 << "Tests a condition with a url matcher, for a non-matching value "
194 << "that would match a different condition.";
195 url_matches.insert(2);
196 EXPECT_TRUE(result->IsFulfilled(2, match_data))
197 << "Tests with 2 elements in the match set.";
199 // Check the condition sets:
200 URLMatcherConditionSet::Vector condition_sets;
201 result->GetURLMatcherConditionSets(&condition_sets);
202 ASSERT_EQ(3U, condition_sets.size());
203 EXPECT_EQ(1, condition_sets[0]->id());
204 EXPECT_EQ(2, condition_sets[1]->id());
205 EXPECT_EQ(3, condition_sets[2]->id());
210 class SummingAction : public base::RefCounted<SummingAction> {
212 typedef int ApplyInfo;
214 SummingAction(int increment, int min_priority)
215 : increment_(increment), min_priority_(min_priority) {}
217 static scoped_refptr<const SummingAction> Create(const Extension* extension,
218 const base::Value& action,
222 int min_priority = 0;
223 const base::DictionaryValue* dict = NULL;
224 EXPECT_TRUE(action.GetAsDictionary(&dict));
225 if (dict->HasKey("error")) {
226 EXPECT_TRUE(dict->GetString("error", error));
227 return scoped_refptr<const SummingAction>(NULL);
229 if (dict->HasKey("bad")) {
231 return scoped_refptr<const SummingAction>(NULL);
234 EXPECT_TRUE(dict->GetInteger("value", &increment));
235 dict->GetInteger("priority", &min_priority);
236 return scoped_refptr<const SummingAction>(
237 new SummingAction(increment, min_priority));
240 void Apply(const std::string& extension_id,
241 const base::Time& install_time,
246 int increment() const { return increment_; }
247 int minimum_priority() const {
248 return min_priority_;
252 friend class base::RefCounted<SummingAction>;
253 virtual ~SummingAction() {}
258 typedef DeclarativeActionSet<SummingAction> SummingActionSet;
260 TEST(DeclarativeActionTest, ErrorActionSet) {
261 SummingActionSet::AnyVector actions;
262 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}")));
263 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"error\": \"the error\"}")));
267 scoped_ptr<SummingActionSet> result =
268 SummingActionSet::Create(NULL, actions, &error, &bad);
269 EXPECT_EQ("the error", error);
271 EXPECT_FALSE(result);
274 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}")));
275 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad\": 3}")));
276 result = SummingActionSet::Create(NULL, actions, &error, &bad);
277 EXPECT_EQ("", error);
279 EXPECT_FALSE(result);
282 TEST(DeclarativeActionTest, ApplyActionSet) {
283 SummingActionSet::AnyVector actions;
284 actions.push_back(ScopedToLinkedPtr(ParseJson(
286 " \"priority\": 5}")));
287 actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 2}")));
292 scoped_ptr<SummingActionSet> result =
293 SummingActionSet::Create(NULL, actions, &error, &bad);
294 EXPECT_EQ("", error);
297 EXPECT_EQ(2u, result->actions().size());
300 result->Apply("ext_id", base::Time(), &sum);
302 EXPECT_EQ(5, result->GetMinimumPriority());
305 TEST(DeclarativeRuleTest, Create) {
306 typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule;
307 linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule);
308 ASSERT_TRUE(Rule::JsonRule::Populate(
310 " \"id\": \"rule1\", \n"
311 " \"conditions\": [ \n"
312 " {\"url_id\": 1, \"max\": 3}, \n"
313 " {\"url_id\": 2, \"max\": 5}, \n"
320 " \"priority\": 200 \n"
324 const char kExtensionId[] = "ext1";
325 scoped_refptr<Extension> extension = ExtensionBuilder()
326 .SetManifest(SimpleManifest())
330 base::Time install_time = base::Time::Now();
334 scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(),
338 Rule::ConsistencyChecker(),
340 EXPECT_EQ("", error);
341 ASSERT_TRUE(rule.get());
343 EXPECT_EQ(kExtensionId, rule->id().first);
344 EXPECT_EQ("rule1", rule->id().second);
346 EXPECT_EQ(200, rule->priority());
348 const Rule::ConditionSet& condition_set = rule->conditions();
349 const Rule::ConditionSet::Conditions& conditions =
350 condition_set.conditions();
351 ASSERT_EQ(2u, conditions.size());
352 EXPECT_EQ(3, conditions[0]->max_value);
353 EXPECT_EQ(5, conditions[1]->max_value);
355 const Rule::ActionSet& action_set = rule->actions();
356 const Rule::ActionSet::Actions& actions = action_set.actions();
357 ASSERT_EQ(1u, actions.size());
358 EXPECT_EQ(2, actions[0]->increment());
365 bool AtLeastOneCondition(
366 const DeclarativeConditionSet<FulfillableCondition>* conditions,
367 const DeclarativeActionSet<SummingAction>* actions,
368 std::string* error) {
369 if (conditions->conditions().empty()) {
370 *error = "No conditions";
376 TEST(DeclarativeRuleTest, CheckConsistency) {
377 typedef DeclarativeRule<FulfillableCondition, SummingAction> Rule;
380 linked_ptr<Rule::JsonRule> json_rule(new Rule::JsonRule);
381 const char kExtensionId[] = "ext1";
382 scoped_refptr<Extension> extension = ExtensionBuilder()
383 .SetManifest(SimpleManifest())
387 ASSERT_TRUE(Rule::JsonRule::Populate(
389 " \"id\": \"rule1\", \n"
390 " \"conditions\": [ \n"
391 " {\"url_id\": 1, \"max\": 3}, \n"
392 " {\"url_id\": 2, \"max\": 5}, \n"
399 " \"priority\": 200 \n"
402 scoped_ptr<Rule> rule(Rule::Create(matcher.condition_factory(),
406 base::Bind(AtLeastOneCondition),
409 EXPECT_EQ("", error);
411 ASSERT_TRUE(Rule::JsonRule::Populate(
413 " \"id\": \"rule1\", \n"
414 " \"conditions\": [ \n"
421 " \"priority\": 200 \n"
424 rule = Rule::Create(matcher.condition_factory(),
428 base::Bind(AtLeastOneCondition),
431 EXPECT_EQ("No conditions", error);
434 } // namespace extensions