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 "extensions/browser/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 "components/url_matcher/url_matcher_constants.h"
12 #include "extensions/common/extension_builder.h"
13 #include "testing/gmock/include/gmock/gmock.h"
14 #include "testing/gtest/include/gtest/gtest.h"
16 using base::test::ParseJson
;
17 using url_matcher::URLMatcher
;
18 using url_matcher::URLMatcherConditionFactory
;
19 using url_matcher::URLMatcherConditionSet
;
21 namespace extensions
{
26 linked_ptr
<T
> ScopedToLinkedPtr(scoped_ptr
<T
> ptr
) {
27 return linked_ptr
<T
>(ptr
.release());
30 scoped_ptr
<base::DictionaryValue
> SimpleManifest() {
31 return DictionaryBuilder()
32 .Set("name", "extension")
33 .Set("manifest_version", 2)
34 .Set("version", "1.0")
40 struct RecordingCondition
{
41 typedef int MatchData
;
43 URLMatcherConditionFactory
* factory
;
44 scoped_ptr
<base::Value
> value
;
46 void GetURLMatcherConditionSets(
47 URLMatcherConditionSet::Vector
* condition_sets
) const {
51 static scoped_ptr
<RecordingCondition
> Create(
52 const Extension
* extension
,
53 URLMatcherConditionFactory
* url_matcher_condition_factory
,
54 const base::Value
& condition
,
56 const base::DictionaryValue
* dict
= NULL
;
57 if (condition
.GetAsDictionary(&dict
) && dict
->HasKey("bad_key")) {
58 *error
= "Found error key";
59 return scoped_ptr
<RecordingCondition
>();
62 scoped_ptr
<RecordingCondition
> result(new RecordingCondition());
63 result
->factory
= url_matcher_condition_factory
;
64 result
->value
.reset(condition
.DeepCopy());
68 typedef DeclarativeConditionSet
<RecordingCondition
> RecordingConditionSet
;
70 TEST(DeclarativeConditionTest
, ErrorConditionSet
) {
72 RecordingConditionSet::Values conditions
;
73 conditions
.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}")));
74 conditions
.push_back(ScopedToLinkedPtr(ParseJson("{\"bad_key\": 2}")));
77 scoped_ptr
<RecordingConditionSet
> result
= RecordingConditionSet::Create(
78 NULL
, matcher
.condition_factory(), conditions
, &error
);
79 EXPECT_EQ("Found error key", error
);
83 TEST(DeclarativeConditionTest
, CreateConditionSet
) {
85 RecordingConditionSet::Values conditions
;
86 conditions
.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}")));
87 conditions
.push_back(ScopedToLinkedPtr(ParseJson("[\"val1\", 2]")));
91 scoped_ptr
<RecordingConditionSet
> result
= RecordingConditionSet::Create(
92 NULL
, matcher
.condition_factory(), conditions
, &error
);
95 EXPECT_EQ(2u, result
->conditions().size());
97 EXPECT_EQ(matcher
.condition_factory(), result
->conditions()[0]->factory
);
98 EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals(
99 result
->conditions()[0]->value
.get()));
102 struct FulfillableCondition
{
105 const std::set
<URLMatcherConditionSet::ID
>& url_matches
;
108 scoped_refptr
<URLMatcherConditionSet
> condition_set
;
109 int condition_set_id
;
112 URLMatcherConditionSet::ID
url_matcher_condition_set_id() const {
113 return condition_set_id
;
116 scoped_refptr
<URLMatcherConditionSet
> url_matcher_condition_set() const {
117 return condition_set
;
120 void GetURLMatcherConditionSets(
121 URLMatcherConditionSet::Vector
* condition_sets
) const {
122 if (condition_set
.get())
123 condition_sets
->push_back(condition_set
);
126 bool IsFulfilled(const MatchData
& match_data
) const {
127 if (condition_set_id
!= -1 &&
128 !ContainsKey(match_data
.url_matches
, condition_set_id
))
130 return match_data
.value
<= max_value
;
133 static scoped_ptr
<FulfillableCondition
> Create(
134 const Extension
* extension
,
135 URLMatcherConditionFactory
* url_matcher_condition_factory
,
136 const base::Value
& condition
,
137 std::string
* error
) {
138 scoped_ptr
<FulfillableCondition
> result(new FulfillableCondition());
139 const base::DictionaryValue
* dict
;
140 if (!condition
.GetAsDictionary(&dict
)) {
141 *error
= "Expected dict";
142 return result
.Pass();
144 if (!dict
->GetInteger("url_id", &result
->condition_set_id
))
145 result
->condition_set_id
= -1;
146 if (!dict
->GetInteger("max", &result
->max_value
))
147 *error
= "Expected integer at ['max']";
148 if (result
->condition_set_id
!= -1) {
149 result
->condition_set
= new URLMatcherConditionSet(
150 result
->condition_set_id
,
151 URLMatcherConditionSet::Conditions());
153 return result
.Pass();
157 TEST(DeclarativeConditionTest
, FulfillConditionSet
) {
158 typedef DeclarativeConditionSet
<FulfillableCondition
> FulfillableConditionSet
;
159 FulfillableConditionSet::Values conditions
;
160 conditions
.push_back(ScopedToLinkedPtr(ParseJson(
161 "{\"url_id\": 1, \"max\": 3}")));
162 conditions
.push_back(ScopedToLinkedPtr(ParseJson(
163 "{\"url_id\": 2, \"max\": 5}")));
164 conditions
.push_back(ScopedToLinkedPtr(ParseJson(
165 "{\"url_id\": 3, \"max\": 1}")));
166 conditions
.push_back(ScopedToLinkedPtr(ParseJson(
167 "{\"max\": -5}"))); // No url.
171 scoped_ptr
<FulfillableConditionSet
> result
=
172 FulfillableConditionSet::Create(NULL
, NULL
, conditions
, &error
);
173 ASSERT_EQ("", error
);
175 EXPECT_EQ(4u, result
->conditions().size());
177 std::set
<URLMatcherConditionSet::ID
> url_matches
;
178 FulfillableCondition::MatchData match_data
= { 0, url_matches
};
179 EXPECT_FALSE(result
->IsFulfilled(1, match_data
))
180 << "Testing an ID that's not in url_matches forwards to the Condition, "
181 << "which doesn't match.";
182 EXPECT_FALSE(result
->IsFulfilled(-1, match_data
))
183 << "Testing the 'no ID' value tries to match the 4th condition, but "
184 << "its max is too low.";
185 match_data
.value
= -5;
186 EXPECT_TRUE(result
->IsFulfilled(-1, match_data
))
187 << "Testing the 'no ID' value tries to match the 4th condition, and "
188 << "this value is low enough.";
190 url_matches
.insert(1);
191 match_data
.value
= 3;
192 EXPECT_TRUE(result
->IsFulfilled(1, match_data
))
193 << "Tests a condition with a url matcher, for a matching value.";
194 match_data
.value
= 4;
195 EXPECT_FALSE(result
->IsFulfilled(1, match_data
))
196 << "Tests a condition with a url matcher, for a non-matching value "
197 << "that would match a different condition.";
198 url_matches
.insert(2);
199 EXPECT_TRUE(result
->IsFulfilled(2, match_data
))
200 << "Tests with 2 elements in the match set.";
202 // Check the condition sets:
203 URLMatcherConditionSet::Vector condition_sets
;
204 result
->GetURLMatcherConditionSets(&condition_sets
);
205 ASSERT_EQ(3U, condition_sets
.size());
206 EXPECT_EQ(1, condition_sets
[0]->id());
207 EXPECT_EQ(2, condition_sets
[1]->id());
208 EXPECT_EQ(3, condition_sets
[2]->id());
213 class SummingAction
: public base::RefCounted
<SummingAction
> {
215 typedef int ApplyInfo
;
217 SummingAction(int increment
, int min_priority
)
218 : increment_(increment
), min_priority_(min_priority
) {}
220 static scoped_refptr
<const SummingAction
> Create(
221 content::BrowserContext
* browser_context
,
222 const Extension
* extension
,
223 const base::Value
& action
,
227 int min_priority
= 0;
228 const base::DictionaryValue
* dict
= NULL
;
229 EXPECT_TRUE(action
.GetAsDictionary(&dict
));
230 if (dict
->HasKey("error")) {
231 EXPECT_TRUE(dict
->GetString("error", error
));
232 return scoped_refptr
<const SummingAction
>(NULL
);
234 if (dict
->HasKey("bad")) {
236 return scoped_refptr
<const SummingAction
>(NULL
);
239 EXPECT_TRUE(dict
->GetInteger("value", &increment
));
240 dict
->GetInteger("priority", &min_priority
);
241 return scoped_refptr
<const SummingAction
>(
242 new SummingAction(increment
, min_priority
));
245 void Apply(const std::string
& extension_id
,
246 const base::Time
& install_time
,
251 int increment() const { return increment_
; }
252 int minimum_priority() const {
253 return min_priority_
;
257 friend class base::RefCounted
<SummingAction
>;
258 virtual ~SummingAction() {}
263 typedef DeclarativeActionSet
<SummingAction
> SummingActionSet
;
265 TEST(DeclarativeActionTest
, ErrorActionSet
) {
266 SummingActionSet::Values actions
;
267 actions
.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}")));
268 actions
.push_back(ScopedToLinkedPtr(ParseJson("{\"error\": \"the error\"}")));
272 scoped_ptr
<SummingActionSet
> result
=
273 SummingActionSet::Create(NULL
, NULL
, actions
, &error
, &bad
);
274 EXPECT_EQ("the error", error
);
276 EXPECT_FALSE(result
);
279 actions
.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}")));
280 actions
.push_back(ScopedToLinkedPtr(ParseJson("{\"bad\": 3}")));
281 result
= SummingActionSet::Create(NULL
, NULL
, actions
, &error
, &bad
);
282 EXPECT_EQ("", error
);
284 EXPECT_FALSE(result
);
287 TEST(DeclarativeActionTest
, ApplyActionSet
) {
288 SummingActionSet::Values actions
;
289 actions
.push_back(ScopedToLinkedPtr(ParseJson(
291 " \"priority\": 5}")));
292 actions
.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 2}")));
297 scoped_ptr
<SummingActionSet
> result
=
298 SummingActionSet::Create(NULL
, NULL
, actions
, &error
, &bad
);
299 EXPECT_EQ("", error
);
302 EXPECT_EQ(2u, result
->actions().size());
305 result
->Apply("ext_id", base::Time(), &sum
);
307 EXPECT_EQ(5, result
->GetMinimumPriority());
310 TEST(DeclarativeRuleTest
, Create
) {
311 typedef DeclarativeRule
<FulfillableCondition
, SummingAction
> Rule
;
312 linked_ptr
<Rule::JsonRule
> json_rule(new Rule::JsonRule
);
313 ASSERT_TRUE(Rule::JsonRule::Populate(
315 " \"id\": \"rule1\", \n"
316 " \"conditions\": [ \n"
317 " {\"url_id\": 1, \"max\": 3}, \n"
318 " {\"url_id\": 2, \"max\": 5}, \n"
325 " \"priority\": 200 \n"
329 const char kExtensionId
[] = "ext1";
330 scoped_refptr
<Extension
> extension
= ExtensionBuilder()
331 .SetManifest(SimpleManifest())
335 base::Time install_time
= base::Time::Now();
339 scoped_ptr
<Rule
> rule(Rule::Create(matcher
.condition_factory(),
344 Rule::ConsistencyChecker(),
346 EXPECT_EQ("", error
);
347 ASSERT_TRUE(rule
.get());
349 EXPECT_EQ(kExtensionId
, rule
->id().first
);
350 EXPECT_EQ("rule1", rule
->id().second
);
352 EXPECT_EQ(200, rule
->priority());
354 const Rule::ConditionSet
& condition_set
= rule
->conditions();
355 const Rule::ConditionSet::Conditions
& conditions
=
356 condition_set
.conditions();
357 ASSERT_EQ(2u, conditions
.size());
358 EXPECT_EQ(3, conditions
[0]->max_value
);
359 EXPECT_EQ(5, conditions
[1]->max_value
);
361 const Rule::ActionSet
& action_set
= rule
->actions();
362 const Rule::ActionSet::Actions
& actions
= action_set
.actions();
363 ASSERT_EQ(1u, actions
.size());
364 EXPECT_EQ(2, actions
[0]->increment());
371 bool AtLeastOneCondition(
372 const DeclarativeConditionSet
<FulfillableCondition
>* conditions
,
373 const DeclarativeActionSet
<SummingAction
>* actions
,
374 std::string
* error
) {
375 if (conditions
->conditions().empty()) {
376 *error
= "No conditions";
382 TEST(DeclarativeRuleTest
, CheckConsistency
) {
383 typedef DeclarativeRule
<FulfillableCondition
, SummingAction
> Rule
;
386 linked_ptr
<Rule::JsonRule
> json_rule(new Rule::JsonRule
);
387 const char kExtensionId
[] = "ext1";
388 scoped_refptr
<Extension
> extension
= ExtensionBuilder()
389 .SetManifest(SimpleManifest())
393 ASSERT_TRUE(Rule::JsonRule::Populate(
395 " \"id\": \"rule1\", \n"
396 " \"conditions\": [ \n"
397 " {\"url_id\": 1, \"max\": 3}, \n"
398 " {\"url_id\": 2, \"max\": 5}, \n"
405 " \"priority\": 200 \n"
408 scoped_ptr
<Rule
> rule(Rule::Create(matcher
.condition_factory(),
413 base::Bind(AtLeastOneCondition
),
416 EXPECT_EQ("", error
);
418 ASSERT_TRUE(Rule::JsonRule::Populate(
420 " \"id\": \"rule1\", \n"
421 " \"conditions\": [ \n"
428 " \"priority\": 200 \n"
431 rule
= Rule::Create(matcher
.condition_factory(),
436 base::Bind(AtLeastOneCondition
),
439 EXPECT_EQ("No conditions", error
);
442 } // namespace extensions