| // Copyright 2018 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 "components/autofill_assistant/browser/script_precondition.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "components/autofill_assistant/browser/batch_element_checker.h" |
| #include "components/autofill_assistant/browser/mock_run_once_callback.h" |
| #include "components/autofill_assistant/browser/mock_web_controller.h" |
| #include "components/autofill_assistant/browser/service.pb.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/re2/src/re2/re2.h" |
| |
| namespace autofill_assistant { |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::Eq; |
| using ::testing::Invoke; |
| |
| // A callback that expects to be called immediately. |
| // |
| // This relies on ScriptPrecondition and WebController calling the callback |
| // immediately, which is not true in general, but is in this test. |
| class DirectCallback { |
| public: |
| DirectCallback() : was_run_(false), result_(false) {} |
| |
| // Returns a base::OnceCallback. The current instance must exist until |
| // GetResultOrDie is called. |
| base::OnceCallback<void(bool)> Get() { |
| return base::BindOnce(&DirectCallback::Run, base::Unretained(this)); |
| } |
| |
| bool GetResultOrDie() { |
| CHECK(was_run_); |
| return result_; |
| } |
| |
| private: |
| void Run(bool result) { |
| was_run_ = true; |
| result_ = result; |
| } |
| |
| bool was_run_; |
| bool result_; |
| }; |
| |
| class ScriptPreconditionTest : public testing::Test { |
| public: |
| void SetUp() override { |
| ON_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) |
| .WillByDefault(RunOnceCallback<1>(true)); |
| ON_CALL(mock_web_controller_, |
| OnElementCheck(Eq(Selector({"does_not_exist"})), _)) |
| .WillByDefault(RunOnceCallback<1>(false)); |
| |
| SetUrl("http://www.example.com/path"); |
| } |
| |
| protected: |
| void SetUrl(const std::string& url) { url_ = GURL(url); } |
| |
| // Runs the preconditions and returns the result. |
| bool Check(const ScriptPreconditionProto& proto) { |
| auto precondition = ScriptPrecondition::FromProto("unused", proto); |
| if (!precondition) |
| return false; |
| |
| DirectCallback callback; |
| BatchElementChecker batch_checks; |
| precondition->Check(url_, &batch_checks, parameters_, executed_scripts_, |
| callback.Get()); |
| batch_checks.Run(&mock_web_controller_, /* all_done=*/base::DoNothing()); |
| return callback.GetResultOrDie(); |
| } |
| |
| GURL url_; |
| MockWebController mock_web_controller_; |
| std::map<std::string, std::string> parameters_; |
| std::map<std::string, ScriptStatusProto> executed_scripts_; |
| }; |
| |
| TEST_F(ScriptPreconditionTest, NoConditions) { |
| EXPECT_TRUE(Check(ScriptPreconditionProto::default_instance())); |
| } |
| |
| TEST_F(ScriptPreconditionTest, DomainMatch) { |
| ScriptPreconditionProto proto; |
| proto.add_domain("http://match.example.com"); |
| proto.add_domain("http://alsomatch.example.com"); |
| |
| SetUrl("http://match.example.com/path"); |
| EXPECT_TRUE(Check(proto)); |
| |
| // Scheme must match. |
| SetUrl("https://match.example.com/path"); |
| EXPECT_FALSE(Check(proto)) << "Scheme must match."; |
| |
| // Port is ignored. |
| SetUrl("http://match.example.com:8080"); |
| EXPECT_TRUE(Check(proto)) << "Port should be ignored"; |
| |
| SetUrl("http://nomatch.example.com/path"); |
| EXPECT_FALSE(Check(proto)) << "nomatch"; |
| |
| SetUrl("http://alsomatch.example.com/path"); |
| EXPECT_TRUE(Check(proto)) << "Path should be ignored"; |
| |
| SetUrl("http://alsomatch.example.com/path?a=b"); |
| EXPECT_TRUE(Check(proto)) << "Query should be ignored."; |
| } |
| |
| TEST_F(ScriptPreconditionTest, TrailingSlash) { |
| ScriptPreconditionProto proto; |
| proto.add_domain("http://example.com/"); |
| |
| SetUrl("http://example.com/path"); |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, PathFullMatch) { |
| ScriptPreconditionProto proto; |
| proto.add_path_pattern("/match.*"); |
| proto.add_path_pattern("/alsomatch"); |
| |
| SetUrl("http://www.example.com/match1"); |
| EXPECT_TRUE(Check(proto)); |
| |
| SetUrl("http://www.example.com/match123"); |
| EXPECT_TRUE(Check(proto)); |
| |
| SetUrl("http://www.example.com/doesnotmatch"); |
| EXPECT_FALSE(Check(proto)); |
| |
| SetUrl("http://www.example.com/alsomatch"); |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, PathPartialMatchFails) { |
| ScriptPreconditionProto proto; |
| proto.add_path_pattern("/match.*"); |
| proto.add_path_pattern(".*/match"); |
| proto.add_path_pattern("/match"); |
| |
| SetUrl("http://www.example.com/prefix/match/suffix"); |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, PathWithQueryAndRef) { |
| ScriptPreconditionProto proto; |
| proto.add_path_pattern("/hello.*world"); |
| |
| SetUrl("http://www.example.com/hello?q=world"); |
| EXPECT_TRUE(Check(proto)); |
| |
| SetUrl("http://www.example.com/hello#world"); |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, BadPathPattern) { |
| ScriptPreconditionProto proto; |
| proto.add_path_pattern("invalid["); |
| |
| EXPECT_EQ(nullptr, ScriptPrecondition::FromProto("unused", proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, IgnoreEmptyElementsExist) { |
| EXPECT_CALL(mock_web_controller_, OnElementCheck(Eq(Selector({"exists"})), _)) |
| .WillOnce(RunOnceCallback<1>(true)); |
| |
| ScriptPreconditionProto proto; |
| proto.add_elements_exist()->add_selectors("exists"); |
| proto.add_elements_exist(); |
| |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, WrongScriptStatusEqualComparator) { |
| ScriptPreconditionProto proto; |
| |
| ScriptStatusMatchProto* script_status_match = proto.add_script_status_match(); |
| script_status_match->set_script("previous_script_success"); |
| script_status_match->set_comparator(ScriptStatusMatchProto::EQUAL); |
| script_status_match->set_status(SCRIPT_STATUS_NOT_RUN); |
| executed_scripts_["previous_script_success"] = SCRIPT_STATUS_SUCCESS; |
| |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, WrongScriptStatusDifferentComparator) { |
| ScriptPreconditionProto proto; |
| |
| ScriptStatusMatchProto* script_status_match = proto.add_script_status_match(); |
| script_status_match->set_script("previous_script_success"); |
| script_status_match->set_comparator(ScriptStatusMatchProto::DIFFERENT); |
| script_status_match->set_status(SCRIPT_STATUS_NOT_RUN); |
| executed_scripts_["previous_script_success"] = SCRIPT_STATUS_SUCCESS; |
| |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, WrongScriptStatusComparatorNotSet) { |
| ScriptPreconditionProto proto; |
| |
| ScriptStatusMatchProto* script_status_match = proto.add_script_status_match(); |
| script_status_match->set_script("previous_script_success"); |
| script_status_match->set_comparator(ScriptStatusMatchProto::EQUAL); |
| script_status_match->set_status(SCRIPT_STATUS_NOT_RUN); |
| executed_scripts_["previous_script_success"] = SCRIPT_STATUS_SUCCESS; |
| |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, WrongScriptStatus) { |
| ScriptPreconditionProto proto; |
| |
| ScriptStatusMatchProto* script_status_match = proto.add_script_status_match(); |
| script_status_match->set_script("previous_script_success"); |
| script_status_match->set_comparator(ScriptStatusMatchProto::EQUAL); |
| script_status_match->set_status(SCRIPT_STATUS_NOT_RUN); |
| executed_scripts_["previous_script_success"] = SCRIPT_STATUS_SUCCESS; |
| |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, MultipleScriptStatus) { |
| ScriptPreconditionProto proto; |
| |
| ScriptStatusMatchProto* previous1 = proto.add_script_status_match(); |
| previous1->set_script("previous1"); |
| previous1->set_comparator(ScriptStatusMatchProto::EQUAL); |
| previous1->set_status(SCRIPT_STATUS_SUCCESS); |
| |
| ScriptStatusMatchProto* previous2 = proto.add_script_status_match(); |
| previous2->set_script("previous2"); |
| previous2->set_comparator(ScriptStatusMatchProto::DIFFERENT); |
| previous2->set_status(SCRIPT_STATUS_NOT_RUN); |
| |
| executed_scripts_["previous1"] = SCRIPT_STATUS_SUCCESS; |
| |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, ParameterMustExist) { |
| ScriptPreconditionProto proto; |
| ScriptParameterMatchProto* match = proto.add_script_parameter_match(); |
| match->set_name("param"); |
| match->set_exists(true); |
| |
| EXPECT_FALSE(Check(proto)); |
| |
| parameters_["param"] = "exists"; |
| |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, ParameterMustNotExist) { |
| ScriptPreconditionProto proto; |
| ScriptParameterMatchProto* match = proto.add_script_parameter_match(); |
| match->set_name("param"); |
| match->set_exists(false); |
| |
| EXPECT_TRUE(Check(proto)); |
| |
| parameters_["param"] = "exists"; |
| |
| EXPECT_FALSE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, ParameterMustHaveValue) { |
| ScriptPreconditionProto proto; |
| ScriptParameterMatchProto* match = proto.add_script_parameter_match(); |
| match->set_name("param"); |
| match->set_value_equals("value"); |
| |
| EXPECT_FALSE(Check(proto)); |
| |
| parameters_["param"] = "another value"; |
| |
| EXPECT_FALSE(Check(proto)); |
| |
| parameters_["param"] = "value"; |
| EXPECT_TRUE(Check(proto)); |
| } |
| |
| TEST_F(ScriptPreconditionTest, MultipleConditions) { |
| ScriptPreconditionProto proto; |
| proto.add_domain("http://match.example.com"); |
| proto.add_path_pattern("/path"); |
| proto.add_elements_exist()->add_selectors("exists"); |
| |
| // Domain and path don't match. |
| EXPECT_FALSE(Check(proto)); |
| |
| SetUrl("http://match.example.com/path"); |
| EXPECT_TRUE(Check(proto)) << "Domain, path and selector must match."; |
| |
| proto.mutable_elements_exist(0)->set_selectors(0, "does_not_exist"); |
| EXPECT_FALSE(Check(proto)) << "Element can not match."; |
| } |
| |
| } // namespace |
| } // namespace autofill_assistant |