// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/declarative/declarative_rule.h" #include "base/bind.h" #include "base/message_loop.h" #include "base/test/values_test_util.h" #include "base/values.h" #include "extensions/common/matcher/url_matcher_constants.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace extensions { using base::test::ParseJson; namespace { template linked_ptr ScopedToLinkedPtr(scoped_ptr ptr) { return linked_ptr(ptr.release()); } } // namespace struct RecordingCondition { typedef int MatchData; URLMatcherConditionFactory* factory; scoped_ptr value; void GetURLMatcherConditionSets( URLMatcherConditionSet::Vector* condition_sets) const { // No condition sets. } static scoped_ptr Create( URLMatcherConditionFactory* url_matcher_condition_factory, const base::Value& condition, std::string* error) { const base::DictionaryValue* dict = NULL; if (condition.GetAsDictionary(&dict) && dict->HasKey("bad_key")) { *error = "Found error key"; return scoped_ptr(); } scoped_ptr result(new RecordingCondition()); result->factory = url_matcher_condition_factory; result->value.reset(condition.DeepCopy()); return result.Pass(); } }; typedef DeclarativeConditionSet RecordingConditionSet; TEST(DeclarativeConditionTest, ErrorConditionSet) { URLMatcher matcher; RecordingConditionSet::AnyVector conditions; conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad_key\": 2}"))); std::string error; scoped_ptr result = RecordingConditionSet::Create(matcher.condition_factory(), conditions, &error); EXPECT_EQ("Found error key", error); ASSERT_FALSE(result); } TEST(DeclarativeConditionTest, CreateConditionSet) { URLMatcher matcher; RecordingConditionSet::AnyVector conditions; conditions.push_back(ScopedToLinkedPtr(ParseJson("{\"key\": 1}"))); conditions.push_back(ScopedToLinkedPtr(ParseJson("[\"val1\", 2]"))); // Test insertion std::string error; scoped_ptr result = RecordingConditionSet::Create(matcher.condition_factory(), conditions, &error); EXPECT_EQ("", error); ASSERT_TRUE(result); EXPECT_EQ(2u, result->conditions().size()); EXPECT_EQ(matcher.condition_factory(), result->conditions()[0]->factory); EXPECT_TRUE(ParseJson("{\"key\": 1}")->Equals( result->conditions()[0]->value.get())); } struct FulfillableCondition { struct MatchData { int value; const std::set& url_matches; }; scoped_refptr condition_set; int condition_set_id; int max_value; URLMatcherConditionSet::ID url_matcher_condition_set_id() const { return condition_set_id; } scoped_refptr url_matcher_condition_set() const { return condition_set; } void GetURLMatcherConditionSets( URLMatcherConditionSet::Vector* condition_sets) const { if (condition_set) condition_sets->push_back(condition_set); } bool IsFulfilled(const MatchData& match_data) const { if (condition_set_id != -1 && !ContainsKey(match_data.url_matches, condition_set_id)) return false; return match_data.value <= max_value; } static scoped_ptr Create( URLMatcherConditionFactory* /*url_matcher_condition_factory*/, const base::Value& condition, std::string* error) { scoped_ptr result(new FulfillableCondition()); const base::DictionaryValue* dict; if (!condition.GetAsDictionary(&dict)) { *error = "Expected dict"; return result.Pass(); } if (!dict->GetInteger("url_id", &result->condition_set_id)) result->condition_set_id = -1; if (!dict->GetInteger("max", &result->max_value)) *error = "Expected integer at ['max']"; if (result->condition_set_id != -1) { result->condition_set = new URLMatcherConditionSet( result->condition_set_id, URLMatcherConditionSet::Conditions()); } return result.Pass(); } }; TEST(DeclarativeConditionTest, FulfillConditionSet) { typedef DeclarativeConditionSet FulfillableConditionSet; FulfillableConditionSet::AnyVector conditions; conditions.push_back(ScopedToLinkedPtr(ParseJson( "{\"url_id\": 1, \"max\": 3}"))); conditions.push_back(ScopedToLinkedPtr(ParseJson( "{\"url_id\": 2, \"max\": 5}"))); conditions.push_back(ScopedToLinkedPtr(ParseJson( "{\"url_id\": 3, \"max\": 1}"))); conditions.push_back(ScopedToLinkedPtr(ParseJson( "{\"max\": -5}"))); // No url. // Test insertion std::string error; scoped_ptr result = FulfillableConditionSet::Create(NULL, conditions, &error); ASSERT_EQ("", error); ASSERT_TRUE(result); EXPECT_EQ(4u, result->conditions().size()); std::set url_matches; FulfillableCondition::MatchData match_data = { 0, url_matches }; EXPECT_FALSE(result->IsFulfilled(1, match_data)) << "Testing an ID that's not in url_matches forwards to the Condition, " << "which doesn't match."; EXPECT_FALSE(result->IsFulfilled(-1, match_data)) << "Testing the 'no ID' value tries to match the 4th condition, but " << "its max is too low."; match_data.value = -5; EXPECT_TRUE(result->IsFulfilled(-1, match_data)) << "Testing the 'no ID' value tries to match the 4th condition, and " << "this value is low enough."; url_matches.insert(1); match_data.value = 3; EXPECT_TRUE(result->IsFulfilled(1, match_data)) << "Tests a condition with a url matcher, for a matching value."; match_data.value = 4; EXPECT_FALSE(result->IsFulfilled(1, match_data)) << "Tests a condition with a url matcher, for a non-matching value " << "that would match a different condition."; url_matches.insert(2); EXPECT_TRUE(result->IsFulfilled(2, match_data)) << "Tests with 2 elements in the match set."; // Check the condition sets: URLMatcherConditionSet::Vector condition_sets; result->GetURLMatcherConditionSets(&condition_sets); ASSERT_EQ(3U, condition_sets.size()); EXPECT_EQ(1, condition_sets[0]->id()); EXPECT_EQ(2, condition_sets[1]->id()); EXPECT_EQ(3, condition_sets[2]->id()); } // DeclarativeAction struct SummingAction { typedef int ApplyInfo; int increment; int min_priority; static scoped_ptr Create(const base::Value& action, std::string* error, bool* bad_message) { scoped_ptr result(new SummingAction()); const base::DictionaryValue* dict = NULL; EXPECT_TRUE(action.GetAsDictionary(&dict)); if (dict->HasKey("error")) { EXPECT_TRUE(dict->GetString("error", error)); return result.Pass(); } if (dict->HasKey("bad")) { *bad_message = true; return result.Pass(); } EXPECT_TRUE(dict->GetInteger("value", &result->increment)); dict->GetInteger("priority", &result->min_priority); return result.Pass(); } void Apply(const std::string& extension_id, const base::Time& install_time, int* sum) const { *sum += increment; } int minimum_priority() const { return min_priority; } }; typedef DeclarativeActionSet SummingActionSet; TEST(DeclarativeActionTest, ErrorActionSet) { SummingActionSet::AnyVector actions; actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); actions.push_back(ScopedToLinkedPtr(ParseJson("{\"error\": \"the error\"}"))); std::string error; bool bad = false; scoped_ptr result = SummingActionSet::Create(actions, &error, &bad); EXPECT_EQ("the error", error); EXPECT_FALSE(bad); EXPECT_FALSE(result); actions.clear(); actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 1}"))); actions.push_back(ScopedToLinkedPtr(ParseJson("{\"bad\": 3}"))); result = SummingActionSet::Create(actions, &error, &bad); EXPECT_EQ("", error); EXPECT_TRUE(bad); EXPECT_FALSE(result); } TEST(DeclarativeActionTest, ApplyActionSet) { SummingActionSet::AnyVector actions; actions.push_back(ScopedToLinkedPtr(ParseJson( "{\"value\": 1," " \"priority\": 5}"))); actions.push_back(ScopedToLinkedPtr(ParseJson("{\"value\": 2}"))); // Test insertion std::string error; bool bad = false; scoped_ptr result = SummingActionSet::Create(actions, &error, &bad); EXPECT_EQ("", error); EXPECT_FALSE(bad); ASSERT_TRUE(result); EXPECT_EQ(2u, result->actions().size()); int sum = 0; result->Apply("ext_id", base::Time(), &sum); EXPECT_EQ(3, sum); EXPECT_EQ(5, result->GetMinimumPriority()); } TEST(DeclarativeRuleTest, Create) { typedef DeclarativeRule Rule; linked_ptr json_rule(new Rule::JsonRule); ASSERT_TRUE(Rule::JsonRule::Populate( *ParseJson("{ \n" " \"id\": \"rule1\", \n" " \"conditions\": [ \n" " {\"url_id\": 1, \"max\": 3}, \n" " {\"url_id\": 2, \"max\": 5}, \n" " ], \n" " \"actions\": [ \n" " { \n" " \"value\": 2 \n" " } \n" " ], \n" " \"priority\": 200 \n" "}"), json_rule.get())); const char kExtensionId[] = "ext1"; base::Time install_time = base::Time::Now(); URLMatcher matcher; std::string error; scoped_ptr rule(Rule::Create(matcher.condition_factory(), kExtensionId, install_time, json_rule, Rule::ConsistencyChecker(), &error)); EXPECT_EQ("", error); ASSERT_TRUE(rule.get()); EXPECT_EQ(kExtensionId, rule->id().first); EXPECT_EQ("rule1", rule->id().second); EXPECT_EQ(200, rule->priority()); const Rule::ConditionSet& condition_set = rule->conditions(); const Rule::ConditionSet::Conditions& conditions = condition_set.conditions(); ASSERT_EQ(2u, conditions.size()); EXPECT_EQ(3, conditions[0]->max_value); EXPECT_EQ(5, conditions[1]->max_value); const Rule::ActionSet& action_set = rule->actions(); const Rule::ActionSet::Actions& actions = action_set.actions(); ASSERT_EQ(1u, actions.size()); EXPECT_EQ(2, actions[0]->increment); int sum = 0; rule->Apply(&sum); EXPECT_EQ(2, sum); } bool AtLeastOneCondition( const DeclarativeConditionSet* conditions, const DeclarativeActionSet* actions, std::string* error) { if (conditions->conditions().empty()) { *error = "No conditions"; return false; } return true; } TEST(DeclarativeRuleTest, CheckConsistency) { typedef DeclarativeRule Rule; URLMatcher matcher; std::string error; linked_ptr json_rule(new Rule::JsonRule); const char kExtensionId[] = "ext1"; ASSERT_TRUE(Rule::JsonRule::Populate( *ParseJson("{ \n" " \"id\": \"rule1\", \n" " \"conditions\": [ \n" " {\"url_id\": 1, \"max\": 3}, \n" " {\"url_id\": 2, \"max\": 5}, \n" " ], \n" " \"actions\": [ \n" " { \n" " \"value\": 2 \n" " } \n" " ], \n" " \"priority\": 200 \n" "}"), json_rule.get())); scoped_ptr rule( Rule::Create(matcher.condition_factory(), kExtensionId, base::Time(), json_rule, base::Bind(AtLeastOneCondition), &error)); EXPECT_TRUE(rule); EXPECT_EQ("", error); ASSERT_TRUE(Rule::JsonRule::Populate( *ParseJson("{ \n" " \"id\": \"rule1\", \n" " \"conditions\": [ \n" " ], \n" " \"actions\": [ \n" " { \n" " \"value\": 2 \n" " } \n" " ], \n" " \"priority\": 200 \n" "}"), json_rule.get())); rule = Rule::Create(matcher.condition_factory(), kExtensionId, base::Time(), json_rule, base::Bind(AtLeastOneCondition), &error); EXPECT_FALSE(rule); EXPECT_EQ("No conditions", error); } } // namespace extensions