blob: 06f013811dcb1886b6fd7155781a46af403ec0a1 [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/protocol_utils.h"
#include "base/macros.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/selector.h"
#include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"
namespace autofill_assistant {
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAreArray;
class ProtocolUtilsTest : public testing::Test {
protected:
ProtocolUtilsTest() {
client_context_proto_.set_experiment_ids("1,2,3");
client_context_proto_.set_is_cct(true);
client_context_proto_.mutable_chrome()->set_chrome_version("v");
auto* device_context = client_context_proto_.mutable_device_context();
device_context->mutable_version()->set_sdk_int(1);
device_context->set_manufacturer("ma");
device_context->set_model("mo");
client_context_proto_.set_is_onboarding_shown(false);
client_context_proto_.set_is_direct_action(false);
client_context_proto_.set_accounts_matching_status(
ClientContextProto::UNKNOWN);
}
~ProtocolUtilsTest() override {}
ClientContextProto client_context_proto_;
};
void AssertClientContext(const ClientContextProto& context) {
EXPECT_EQ("1,2,3", context.experiment_ids());
EXPECT_TRUE(context.is_cct());
EXPECT_EQ("v", context.chrome().chrome_version());
EXPECT_EQ(1, context.device_context().version().sdk_int());
EXPECT_EQ("ma", context.device_context().manufacturer());
EXPECT_EQ("mo", context.device_context().model());
EXPECT_FALSE(context.is_onboarding_shown());
EXPECT_FALSE(context.is_direct_action());
EXPECT_THAT(context.accounts_matching_status(),
Eq(ClientContextProto::UNKNOWN));
}
TEST_F(ProtocolUtilsTest, ScriptMissingPath) {
SupportedScriptProto script;
script.mutable_presentation()->mutable_chip()->set_text("missing path");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script, &scripts);
EXPECT_THAT(scripts, IsEmpty());
}
TEST_F(ProtocolUtilsTest, MinimalValidScript) {
SupportedScriptProto script;
script.set_path("path");
script.mutable_presentation()->mutable_chip()->set_text("name");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script, &scripts);
ASSERT_THAT(scripts, SizeIs(1));
EXPECT_EQ("path", scripts[0]->handle.path);
EXPECT_EQ("name", scripts[0]->handle.chip.text);
EXPECT_NE(nullptr, scripts[0]->precondition);
}
TEST_F(ProtocolUtilsTest, AllowInterruptsWithNoName) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->set_autostart(true);
presentation->set_initial_prompt("prompt");
presentation->set_interrupt(true);
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
ASSERT_THAT(scripts, SizeIs(1));
EXPECT_EQ("path", scripts[0]->handle.path);
EXPECT_EQ("", scripts[0]->handle.chip.text);
EXPECT_TRUE(scripts[0]->handle.interrupt);
}
TEST_F(ProtocolUtilsTest, InterruptsCannotBeAutostart) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->set_autostart(true);
presentation->set_interrupt(true);
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
ASSERT_THAT(scripts, SizeIs(1));
EXPECT_FALSE(scripts[0]->handle.autostart);
EXPECT_TRUE(scripts[0]->handle.interrupt);
}
TEST_F(ProtocolUtilsTest, CreateInitialScriptActionsRequest) {
ScriptParameters parameters = {{{"key_a", "value_a"}, {"key_b", "value_b"}}};
ScriptActionRequestProto request;
ScriptStoreConfig config;
config.set_bundle_path("bundle/path");
config.set_bundle_version(12);
EXPECT_TRUE(
request.ParseFromString(ProtocolUtils::CreateInitialScriptActionsRequest(
"script_path", GURL("http://example.com/"), "global_payload",
"script_payload", client_context_proto_, parameters,
base::Optional<ScriptStoreConfig>(config))));
const InitialScriptActionsRequestProto& initial = request.initial_request();
EXPECT_THAT(initial.query().script_path(), ElementsAre("script_path"));
EXPECT_EQ(initial.query().url(), "http://example.com/");
EXPECT_THAT(initial.script_parameters(),
UnorderedElementsAreArray(parameters.ToProto()));
AssertClientContext(request.client_context());
EXPECT_EQ("global_payload", request.global_payload());
EXPECT_EQ("script_payload", request.script_payload());
EXPECT_EQ("bundle/path", initial.script_store_config().bundle_path());
EXPECT_EQ(12, initial.script_store_config().bundle_version());
}
TEST_F(ProtocolUtilsTest, CreateNextScriptActionsRequest) {
ScriptActionRequestProto request;
std::vector<ProcessedActionProto> processed_actions;
processed_actions.emplace_back(ProcessedActionProto());
EXPECT_TRUE(
request.ParseFromString(ProtocolUtils::CreateNextScriptActionsRequest(
"global_payload", "script_payload", processed_actions,
RoundtripTimingStats(), client_context_proto_)));
AssertClientContext(request.client_context());
EXPECT_EQ(1, request.next_request().processed_actions().size());
}
TEST_F(ProtocolUtilsTest, CreateGetScriptsRequest) {
ScriptParameters parameters = {{{"key_a", "value_a"}, {"key_b", "value_b"}}};
SupportsScriptRequestProto request;
EXPECT_TRUE(request.ParseFromString(ProtocolUtils::CreateGetScriptsRequest(
GURL("http://example.com/"), client_context_proto_, parameters)));
AssertClientContext(request.client_context());
EXPECT_THAT(request.script_parameters(),
UnorderedElementsAreArray(parameters.ToProto()));
EXPECT_EQ("http://example.com/", request.url());
}
TEST_F(ProtocolUtilsTest, AddScriptIgnoreInvalid) {
SupportedScriptProto script_proto;
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
EXPECT_TRUE(scripts.empty());
}
TEST_F(ProtocolUtilsTest, AddScriptWithChip) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->mutable_chip()->set_text("name");
presentation->set_initial_prompt("prompt");
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
std::unique_ptr<Script> script = std::move(scripts[0]);
EXPECT_NE(nullptr, script);
EXPECT_EQ("path", script->handle.path);
EXPECT_EQ("name", script->handle.chip.text);
EXPECT_EQ("prompt", script->handle.initial_prompt);
EXPECT_FALSE(script->handle.autostart);
EXPECT_NE(nullptr, script->precondition);
}
TEST_F(ProtocolUtilsTest, AddScriptWithDirectAction) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->mutable_direct_action()->add_names("action_name");
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
std::unique_ptr<Script> script = std::move(scripts[0]);
EXPECT_NE(nullptr, script);
EXPECT_EQ("path", script->handle.path);
EXPECT_THAT(script->handle.direct_action.names, ElementsAre("action_name"));
EXPECT_TRUE(script->handle.chip.empty());
EXPECT_FALSE(script->handle.autostart);
EXPECT_NE(nullptr, script->precondition);
}
TEST_F(ProtocolUtilsTest, AddAutostartableScript) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->mutable_chip()->set_text("name");
presentation->set_autostart(true);
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
std::unique_ptr<Script> script = std::move(scripts[0]);
EXPECT_NE(nullptr, script);
EXPECT_EQ("path", script->handle.path);
EXPECT_TRUE(script->handle.chip.empty());
EXPECT_TRUE(script->handle.autostart);
EXPECT_NE(nullptr, script->precondition);
}
TEST_F(ProtocolUtilsTest, SkipAutostartableScriptWithoutName) {
SupportedScriptProto script_proto;
script_proto.set_path("path");
auto* presentation = script_proto.mutable_presentation();
presentation->set_autostart(true);
presentation->mutable_precondition()->add_domain("www.example.com");
std::vector<std::unique_ptr<Script>> scripts;
ProtocolUtils::AddScript(script_proto, &scripts);
EXPECT_THAT(scripts, IsEmpty());
}
TEST_F(ProtocolUtilsTest, ParseActionsParseError) {
bool unused;
std::vector<std::unique_ptr<Action>> unused_actions;
std::vector<std::unique_ptr<Script>> unused_scripts;
EXPECT_FALSE(ProtocolUtils::ParseActions(nullptr, "invalid", nullptr, nullptr,
&unused_actions, &unused_scripts,
&unused));
}
TEST_F(ProtocolUtilsTest, ParseActionsValid) {
ActionsResponseProto proto;
proto.set_global_payload("global_payload");
proto.set_script_payload("script_payload");
proto.add_actions()->mutable_tell();
proto.add_actions()->mutable_click();
std::string proto_str;
proto.SerializeToString(&proto_str);
std::string global_payload;
std::string script_payload;
bool should_update_scripts = true;
std::vector<std::unique_ptr<Action>> actions;
std::vector<std::unique_ptr<Script>> scripts;
EXPECT_TRUE(ProtocolUtils::ParseActions(nullptr, proto_str, &global_payload,
&script_payload, &actions, &scripts,
&should_update_scripts));
EXPECT_EQ("global_payload", global_payload);
EXPECT_EQ("script_payload", script_payload);
EXPECT_THAT(actions, SizeIs(2));
EXPECT_FALSE(should_update_scripts);
EXPECT_TRUE(scripts.empty());
}
TEST_F(ProtocolUtilsTest, ParseActionsEmptyUpdateScriptList) {
ActionsResponseProto proto;
proto.mutable_update_script_list();
std::string proto_str;
proto.SerializeToString(&proto_str);
bool should_update_scripts = false;
std::vector<std::unique_ptr<Script>> scripts;
std::vector<std::unique_ptr<Action>> unused_actions;
EXPECT_TRUE(ProtocolUtils::ParseActions(
nullptr, proto_str, /* global_payload= */ nullptr,
/* script_payload */ nullptr, &unused_actions, &scripts,
&should_update_scripts));
EXPECT_TRUE(should_update_scripts);
EXPECT_TRUE(scripts.empty());
}
TEST_F(ProtocolUtilsTest, ParseActionsUpdateScriptListFullFeatured) {
ActionsResponseProto proto;
auto* script_list = proto.mutable_update_script_list();
auto* script_a = script_list->add_scripts();
script_a->set_path("a");
auto* presentation = script_a->mutable_presentation();
presentation->mutable_chip()->set_text("name");
presentation->mutable_precondition();
// One invalid script.
script_list->add_scripts();
std::string proto_str;
proto.SerializeToString(&proto_str);
bool should_update_scripts = false;
std::vector<std::unique_ptr<Script>> scripts;
std::vector<std::unique_ptr<Action>> unused_actions;
EXPECT_TRUE(ProtocolUtils::ParseActions(
nullptr, proto_str, /* global_payload= */ nullptr,
/* script_payload= */ nullptr, &unused_actions, &scripts,
&should_update_scripts));
EXPECT_TRUE(should_update_scripts);
EXPECT_THAT(scripts, SizeIs(1));
EXPECT_THAT("a", Eq(scripts[0]->handle.path));
EXPECT_THAT("name", Eq(scripts[0]->handle.chip.text));
}
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsParseError) {
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
std::vector<std::string> additional_allowed_domains;
int interval_ms;
base::Optional<int> timeout_ms;
EXPECT_FALSE(ProtocolUtils::ParseTriggerScripts("invalid", &trigger_scripts,
&additional_allowed_domains,
&interval_ms, &timeout_ms));
EXPECT_TRUE(trigger_scripts.empty());
}
TEST_F(ProtocolUtilsTest, CreateGetTriggerScriptsRequest) {
ScriptParameters parameters = {
{{"key_a", "value_a"}, {"DEBUG_BUNDLE_ID", "123"}}};
GetTriggerScriptsRequestProto request;
EXPECT_TRUE(
request.ParseFromString(ProtocolUtils::CreateGetTriggerScriptsRequest(
GURL("http://example.com/"), client_context_proto_, parameters)));
AssertClientContext(request.client_context());
EXPECT_THAT(request.debug_script_parameters(),
UnorderedElementsAreArray(
ScriptParameters(std::map<std::string, std::string>{
{"DEBUG_BUNDLE_ID", "123"}})
.ToProto()));
EXPECT_EQ("http://example.com/", request.url());
}
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) {
GetTriggerScriptsResponseProto proto;
proto.add_additional_allowed_domains("example.com");
proto.add_additional_allowed_domains("other-example.com");
proto.set_trigger_condition_check_interval_ms(2000);
proto.set_timeout_ms(500000);
TriggerScriptProto trigger_script_1;
*trigger_script_1.mutable_trigger_condition()->mutable_selector() =
ToSelectorProto("fake_element_1");
TriggerScriptProto trigger_script_2;
*proto.add_trigger_scripts() = trigger_script_1;
*proto.add_trigger_scripts() = trigger_script_2;
std::string proto_str;
proto.SerializeToString(&proto_str);
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
std::vector<std::string> additional_allowed_domains;
int interval_ms;
base::Optional<int> timeout_ms;
EXPECT_TRUE(ProtocolUtils::ParseTriggerScripts(proto_str, &trigger_scripts,
&additional_allowed_domains,
&interval_ms, &timeout_ms));
EXPECT_THAT(
trigger_scripts,
ElementsAre(
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_1))),
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_2)))));
EXPECT_THAT(additional_allowed_domains,
ElementsAre("example.com", "other-example.com"));
EXPECT_EQ(interval_ms, 2000);
EXPECT_EQ(timeout_ms, 500000);
}
TEST_F(ProtocolUtilsTest, TurnOffResizeVisualViewport) {
GetTriggerScriptsResponseProto proto;
auto* script1 = proto.add_trigger_scripts();
script1->mutable_user_interface()->set_scroll_to_hide(true);
script1->mutable_user_interface()->set_resize_visual_viewport(true);
auto* script2 = proto.add_trigger_scripts();
script2->mutable_user_interface()->set_resize_visual_viewport(true);
std::string proto_str;
proto.SerializeToString(&proto_str);
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
std::vector<std::string> additional_allowed_domains;
int interval_ms;
base::Optional<int> timeout_ms;
EXPECT_TRUE(ProtocolUtils::ParseTriggerScripts(proto_str, &trigger_scripts,
&additional_allowed_domains,
&interval_ms, &timeout_ms));
ASSERT_THAT(trigger_scripts, SizeIs(2));
EXPECT_TRUE(trigger_scripts[0]->AsProto().user_interface().scroll_to_hide());
EXPECT_FALSE(
trigger_scripts[0]->AsProto().user_interface().resize_visual_viewport());
EXPECT_FALSE(trigger_scripts[1]->AsProto().user_interface().scroll_to_hide());
EXPECT_TRUE(
trigger_scripts[1]->AsProto().user_interface().resize_visual_viewport());
}
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsFailsOnInvalidConditions) {
GetTriggerScriptsResponseProto proto;
TriggerScriptProto trigger_script_1;
TriggerScriptProto trigger_script_2;
trigger_script_2.mutable_trigger_condition()->set_domain_with_scheme(
"invalid");
*proto.add_trigger_scripts() = trigger_script_1;
*proto.add_trigger_scripts() = trigger_script_2;
std::string proto_str;
proto.SerializeToString(&proto_str);
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
std::vector<std::string> additional_allowed_domains;
int interval_ms;
base::Optional<int> timeout_ms;
EXPECT_FALSE(ProtocolUtils::ParseTriggerScripts(proto_str, &trigger_scripts,
&additional_allowed_domains,
&interval_ms, &timeout_ms));
EXPECT_THAT(trigger_scripts, IsEmpty());
}
TEST_F(ProtocolUtilsTest, ValidateTriggerConditionsSimpleConditions) {
TriggerScriptConditionProto condition;
condition.set_path_pattern("(blahblah)*[A-Z]");
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_path_pattern("");
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_path_pattern("[invalid");
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_domain_with_scheme("https://www.example.com");
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_domain_with_scheme("");
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_domain_with_scheme("www.example.com");
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
condition.set_domain_with_scheme("https");
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
}
TEST_F(ProtocolUtilsTest, ValidateTriggerConditionsComplexConditions) {
TriggerScriptConditionProto valid_condition_1;
valid_condition_1.set_path_pattern("pattern1");
TriggerScriptConditionProto valid_condition_2;
valid_condition_2.set_path_pattern("pattern.*");
TriggerScriptConditionProto invalid_condition;
invalid_condition.set_path_pattern("[invalid");
TriggerScriptConditionProto condition;
TriggerScriptConditionsProto valid_conditions;
*valid_conditions.add_conditions() = valid_condition_1;
*valid_conditions.add_conditions() = valid_condition_2;
*condition.mutable_all_of() = valid_conditions;
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
*condition.mutable_any_of() = valid_conditions;
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
*condition.mutable_none_of() = valid_conditions;
EXPECT_TRUE(ProtocolUtils::ValidateTriggerCondition(condition));
TriggerScriptConditionsProto invalid_conditions = valid_conditions;
*invalid_conditions.add_conditions() = invalid_condition;
*condition.mutable_all_of() = invalid_conditions;
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
*condition.mutable_any_of() = invalid_conditions;
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
*condition.mutable_none_of() = invalid_conditions;
EXPECT_FALSE(ProtocolUtils::ValidateTriggerCondition(condition));
}
} // namespace autofill_assistant