Home | History | Annotate | Download | only in declarative
      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