blob: 4802b9e6231f89b841de3d0cd5b4005785fa18e1 [file] [log] [blame]
// 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