| // Copyright 2019 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/user_model.h" |
| #include "base/guid.h" |
| #include "components/autofill/core/browser/autofill_test_utils.h" |
| #include "components/autofill_assistant/browser/mock_user_model_observer.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| namespace autofill_assistant { |
| const char kFakeUrl[] = "https://www.example.com"; |
| |
| using ::testing::_; |
| using ::testing::Eq; |
| using ::testing::InSequence; |
| using ::testing::Pair; |
| using ::testing::Property; |
| using ::testing::SizeIs; |
| using ::testing::StrEq; |
| using ::testing::UnorderedElementsAre; |
| |
| class UserModelTest : public testing::Test { |
| public: |
| UserModelTest() = default; |
| ~UserModelTest() override {} |
| |
| void SetUp() override { model_.AddObserver(&mock_observer_); } |
| |
| void TearDown() override { model_.RemoveObserver(&mock_observer_); } |
| |
| // Provides direct access to the values in the model for testing. |
| const std::map<std::string, ValueProto>& GetValues() const { |
| return model_.values_; |
| } |
| |
| ValueProto CreateStringValue() const { |
| ValueProto value; |
| value.mutable_strings()->add_values("Aurea prima"); |
| value.mutable_strings()->add_values("sata est,"); |
| value.mutable_strings()->add_values("aetas quae"); |
| value.mutable_strings()->add_values("vindice nullo"); |
| value.mutable_strings()->add_values("ü万𠜎"); |
| return value; |
| } |
| |
| ValueProto CreateIntValue() const { |
| ValueProto value; |
| value.mutable_ints()->add_values(1); |
| value.mutable_ints()->add_values(123); |
| value.mutable_ints()->add_values(5); |
| value.mutable_ints()->add_values(-132); |
| return value; |
| } |
| |
| ValueProto CreateBoolValue() const { |
| ValueProto value; |
| value.mutable_booleans()->add_values(true); |
| value.mutable_booleans()->add_values(false); |
| value.mutable_booleans()->add_values(true); |
| value.mutable_booleans()->add_values(true); |
| return value; |
| } |
| |
| protected: |
| UserModel model_; |
| MockUserModelObserver mock_observer_; |
| }; |
| |
| TEST_F(UserModelTest, EmptyValue) { |
| ValueProto value; |
| EXPECT_CALL(mock_observer_, OnValueChanged("identifier", value)).Times(1); |
| model_.SetValue("identifier", value); |
| model_.SetValue("identifier", value); |
| |
| EXPECT_THAT(GetValues(), UnorderedElementsAre(Pair("identifier", value))); |
| } |
| |
| TEST_F(UserModelTest, InsertNewValues) { |
| ValueProto value_a = CreateStringValue(); |
| ValueProto value_b = CreateIntValue(); |
| ValueProto value_c = CreateBoolValue(); |
| |
| testing::InSequence seq; |
| EXPECT_CALL(mock_observer_, OnValueChanged("value_a", value_a)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("value_b", value_b)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("value_c", value_c)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged(_, _)).Times(0); |
| |
| model_.SetValue("value_a", value_a); |
| model_.SetValue("value_b", value_b); |
| model_.SetValue("value_c", value_c); |
| |
| EXPECT_THAT(GetValues(), UnorderedElementsAre(Pair("value_a", value_a), |
| Pair("value_b", value_b), |
| Pair("value_c", value_c))); |
| } |
| |
| TEST_F(UserModelTest, OverwriteWithExistingValueFiresNoChangeEvent) { |
| ValueProto value = CreateStringValue(); |
| EXPECT_CALL(mock_observer_, OnValueChanged("identifier", value)).Times(1); |
| model_.SetValue("identifier", value); |
| |
| ValueProto same_value = CreateStringValue(); |
| model_.SetValue("identifier", same_value); |
| |
| EXPECT_THAT(GetValues(), UnorderedElementsAre(Pair("identifier", value))); |
| } |
| |
| TEST_F(UserModelTest, OverwriteWithDifferentValueFiresChangeEvent) { |
| ValueProto value = CreateStringValue(); |
| EXPECT_CALL(mock_observer_, OnValueChanged("identifier", _)).Times(2); |
| model_.SetValue("identifier", value); |
| |
| ValueProto another_value = CreateStringValue(); |
| another_value.mutable_strings()->add_values("tomato"); |
| model_.SetValue("identifier", another_value); |
| |
| EXPECT_THAT(GetValues(), |
| UnorderedElementsAre(Pair("identifier", another_value))); |
| } |
| |
| TEST_F(UserModelTest, ForceNotificationAlwaysFiresChangeEvent) { |
| testing::InSequence seq; |
| ValueProto value_a = CreateStringValue(); |
| EXPECT_CALL(mock_observer_, OnValueChanged("a", value_a)).Times(1); |
| model_.SetValue("a", value_a); |
| |
| EXPECT_CALL(mock_observer_, OnValueChanged("a", value_a)).Times(0); |
| model_.SetValue("a", value_a); |
| |
| EXPECT_CALL(mock_observer_, OnValueChanged("a", value_a)).Times(1); |
| model_.SetValue("a", value_a, /* force_notification = */ true); |
| } |
| |
| TEST_F(UserModelTest, MergeWithProto) { |
| testing::InSequence seq; |
| ValueProto value_a = CreateStringValue(); |
| ValueProto value_b = CreateIntValue(); |
| ValueProto value_d = CreateBoolValue(); |
| EXPECT_CALL(mock_observer_, OnValueChanged("a", value_a)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("b", value_b)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("c", ValueProto())).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("d", value_d)).Times(1); |
| model_.SetValue("a", value_a); |
| model_.SetValue("b", value_b); |
| model_.SetValue("c", ValueProto()); |
| model_.SetValue("d", value_d); |
| |
| ModelProto proto; |
| ValueProto value_b_changed = value_b; |
| value_b_changed.mutable_ints()->add_values(14); |
| ValueProto value_c_changed = CreateBoolValue(); |
| ValueProto value_e = CreateStringValue(); |
| // Overwrite existing value. |
| auto* value = proto.add_values(); |
| value->set_identifier("b"); |
| *value->mutable_value() = value_b_changed; |
| // Overwrite existing empty value with non-empty value. |
| value = proto.add_values(); |
| value->set_identifier("c"); |
| *value->mutable_value() = value_c_changed; |
| // Does not overwrite existing non-empty value. |
| value = proto.add_values(); |
| value->set_identifier("d"); |
| *value->mutable_value() = ValueProto(); |
| // Inserts new non-empty value. |
| value = proto.add_values(); |
| value->set_identifier("e"); |
| *value->mutable_value() = value_e; |
| // Inserts new empty value. |
| value = proto.add_values(); |
| value->set_identifier("f"); |
| *value->mutable_value() = ValueProto(); |
| |
| EXPECT_CALL(mock_observer_, OnValueChanged("a", _)).Times(0); |
| EXPECT_CALL(mock_observer_, OnValueChanged("b", value_b_changed)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("c", value_c_changed)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("d", _)).Times(0); |
| EXPECT_CALL(mock_observer_, OnValueChanged("e", value_e)).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged("f", ValueProto())).Times(1); |
| EXPECT_CALL(mock_observer_, OnValueChanged(_, _)).Times(0); |
| model_.MergeWithProto(proto, /*force_notification=*/false); |
| |
| EXPECT_THAT(GetValues(), UnorderedElementsAre( |
| Pair("a", value_a), Pair("b", value_b_changed), |
| Pair("c", value_c_changed), Pair("d", value_d), |
| Pair("e", value_e), Pair("f", ValueProto()))); |
| } |
| |
| TEST_F(UserModelTest, UpdateProto) { |
| testing::InSequence seq; |
| ValueProto value_a = CreateStringValue(); |
| ValueProto value_c = CreateBoolValue(); |
| model_.SetValue("a", value_a); |
| model_.SetValue("b", ValueProto()); |
| model_.SetValue("c", value_c); |
| model_.SetValue("d", CreateStringValue()); |
| |
| ModelProto proto; |
| // 'a' in proto is non-empty value and should be overwritten. |
| auto* value = proto.add_values(); |
| value->set_identifier("a"); |
| *value->mutable_value() = CreateBoolValue(); |
| // 'b' in proto is non-empty and should be overwritten with empty value. |
| value = proto.add_values(); |
| value->set_identifier("b"); |
| *value->mutable_value() = CreateIntValue(); |
| // 'c' in proto is default empty and should be overwritten with |value_c|. |
| proto.add_values()->set_identifier("c"); |
| // 'd' is not in the proto and should not be added to the proto. |
| |
| model_.UpdateProto(&proto); |
| EXPECT_THAT( |
| proto.values(), |
| UnorderedElementsAre( |
| AllOf(Property(&ModelProto::ModelValue::identifier, StrEq("a")), |
| Property(&ModelProto::ModelValue::value, Eq(value_a))), |
| AllOf(Property(&ModelProto::ModelValue::identifier, StrEq("b")), |
| Property(&ModelProto::ModelValue::value, Eq(ValueProto()))), |
| AllOf(Property(&ModelProto::ModelValue::identifier, StrEq("c")), |
| Property(&ModelProto::ModelValue::value, Eq(value_c))))); |
| } |
| |
| TEST_F(UserModelTest, SubscriptAccess) { |
| ValueProto value; |
| value.mutable_strings()->add_values("a"); |
| value.mutable_strings()->add_values("b"); |
| value.mutable_strings()->add_values("c"); |
| |
| model_.SetValue("value", value); |
| EXPECT_EQ(model_.GetValue("value"), value); |
| EXPECT_EQ(model_.GetValue("value[0]"), SimpleValue(std::string("a"))); |
| EXPECT_EQ(model_.GetValue("value[1]"), SimpleValue(std::string("b"))); |
| EXPECT_EQ(model_.GetValue("value[2]"), SimpleValue(std::string("c"))); |
| EXPECT_EQ(model_.GetValue("value[001]"), SimpleValue(std::string("b"))); |
| |
| EXPECT_EQ(model_.GetValue("value[3]"), base::nullopt); |
| EXPECT_EQ(model_.GetValue("value[-1]"), base::nullopt); |
| |
| model_.SetValue("index", SimpleValue(0)); |
| EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("a"))); |
| model_.SetValue("index", SimpleValue(1)); |
| EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("b"))); |
| model_.SetValue("index", SimpleValue(2)); |
| EXPECT_EQ(model_.GetValue("value[index]"), SimpleValue(std::string("c"))); |
| model_.SetValue("index", SimpleValue(3)); |
| EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt); |
| model_.SetValue("index", SimpleValue(-1)); |
| EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt); |
| |
| model_.SetValue("index", SimpleValue(0)); |
| EXPECT_EQ(model_.GetValue("value[index[0]]"), SimpleValue(std::string("a"))); |
| model_.SetValue("index", SimpleValue(std::string("not an index"))); |
| EXPECT_EQ(model_.GetValue("value[index]"), base::nullopt); |
| |
| ValueProto indices; |
| indices.mutable_ints()->add_values(2); |
| indices.mutable_ints()->add_values(0); |
| indices.mutable_ints()->add_values(1); |
| model_.SetValue("indices", indices); |
| |
| model_.SetValue("index", SimpleValue(0)); |
| EXPECT_EQ(model_.GetValue("value[indices[index]]"), |
| SimpleValue(std::string("c"))); |
| |
| model_.SetValue("index", SimpleValue(1)); |
| EXPECT_EQ(model_.GetValue("value[indices[index]]"), |
| SimpleValue(std::string("a"))); |
| |
| model_.SetValue("index", SimpleValue(2)); |
| EXPECT_EQ(model_.GetValue("value[indices[index]]"), |
| SimpleValue(std::string("b"))); |
| } |
| |
| TEST_F(UserModelTest, IrregularModelIdentifiers) { |
| ValueProto value; |
| value.mutable_strings()->add_values("a"); |
| value.mutable_strings()->add_values("b"); |
| value.mutable_strings()->add_values("c"); |
| |
| model_.SetValue("normal_identifier", value); |
| model_.SetValue("utf_8_ü万𠜎", value); |
| model_.SetValue("ends_in_bracket]", value); |
| model_.SetValue("contains_[brackets]", value); |
| model_.SetValue("[]", value); |
| model_.SetValue("empty_brackets[]", value); |
| |
| // Retrieving simple values works for any identifiers. |
| EXPECT_EQ(model_.GetValue("normal_identifier"), value); |
| EXPECT_EQ(model_.GetValue("utf_8_ü万𠜎"), value); |
| EXPECT_EQ(model_.GetValue("ends_in_bracket]"), value); |
| EXPECT_EQ(model_.GetValue("contains_[brackets]"), value); |
| EXPECT_EQ(model_.GetValue("[]"), value); |
| EXPECT_EQ(model_.GetValue("empty_brackets[]"), value); |
| |
| // Subscript access is not supported for model identifiers containing |
| // irregular characters (i.e., outside of \w+). |
| EXPECT_EQ(model_.GetValue("normal_identifier[1]"), |
| SimpleValue(std::string("b"))); |
| EXPECT_EQ(model_.GetValue("ends_in_bracket][1]"), base::nullopt); |
| EXPECT_EQ(model_.GetValue("contains_[brackets][1]"), base::nullopt); |
| EXPECT_EQ(model_.GetValue("[][0]"), base::nullopt); |
| EXPECT_EQ(model_.GetValue("empty_brackets[1]"), base::nullopt); |
| EXPECT_EQ(model_.GetValue("empty_brackets[][1]"), base::nullopt); |
| |
| // Subscript access into UTF-8 identifiers is not supported. |
| EXPECT_EQ(model_.GetValue("utf_8_ü万𠜎[1]"), base::nullopt); |
| } |
| |
| TEST_F(UserModelTest, SetCreditCards) { |
| autofill::CreditCard credit_card_a(base::GenerateGUID(), kFakeUrl); |
| autofill::test::SetCreditCardInfo(&credit_card_a, "Marion Mitchell", |
| "4111 1111 1111 1111", "01", "2050", ""); |
| autofill::CreditCard credit_card_b(base::GenerateGUID(), kFakeUrl); |
| autofill::test::SetCreditCardInfo(&credit_card_b, "John Doe", |
| "4111 1111 1111 1111", "01", "2050", ""); |
| auto credit_cards = |
| std::make_unique<std::vector<std::unique_ptr<autofill::CreditCard>>>(); |
| credit_cards->emplace_back( |
| std::make_unique<autofill::CreditCard>(credit_card_a)); |
| credit_cards->emplace_back( |
| std::make_unique<autofill::CreditCard>(credit_card_b)); |
| model_.SetAutofillCreditCards(std::move(credit_cards)); |
| EXPECT_THAT( |
| model_.GetCreditCard(credit_card_a.guid())->Compare(credit_card_a), |
| Eq(0)); |
| EXPECT_THAT( |
| model_.GetCreditCard(credit_card_b.guid())->Compare(credit_card_b), |
| Eq(0)); |
| } |
| |
| TEST_F(UserModelTest, SetProfiles) { |
| autofill::AutofillProfile profile_a(base::GenerateGUID(), kFakeUrl); |
| autofill::test::SetProfileInfo( |
| &profile_a, "Marion", "Mitchell", "Morrison", "marion@me.xyz", "Fox", |
| "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "16505678910"); |
| autofill::AutofillProfile profile_b(base::GenerateGUID(), kFakeUrl); |
| autofill::test::SetProfileInfo(&profile_b, "John", "", "Doe", |
| "editor@gmail.com", "", "203 Barfield Lane", |
| "", "Mountain View", "CA", "94043", "US", |
| "+12345678901"); |
| auto profiles = std::make_unique< |
| std::vector<std::unique_ptr<autofill::AutofillProfile>>>(); |
| profiles->emplace_back( |
| std::make_unique<autofill::AutofillProfile>(profile_a)); |
| profiles->emplace_back( |
| std::make_unique<autofill::AutofillProfile>(profile_b)); |
| model_.SetAutofillProfiles(std::move(profiles)); |
| EXPECT_THAT(model_.GetProfile(profile_a.guid())->Compare(profile_a), Eq(0)); |
| EXPECT_THAT(model_.GetProfile(profile_b.guid())->Compare(profile_b), Eq(0)); |
| } |
| |
| TEST_F(UserModelTest, ClientSideOnlyNotifications) { |
| testing::InSequence seq; |
| model_.SetValue("identifier", |
| SimpleValue(1, /* is_client_side_only = */ false)); |
| EXPECT_CALL(mock_observer_, OnValueChanged("identifier", _)).Times(0); |
| model_.SetValue("identifier", |
| SimpleValue(1, /* is_client_side_only = */ false)); |
| |
| EXPECT_CALL(mock_observer_, OnValueChanged("identifier", _)).Times(1); |
| model_.SetValue("identifier", |
| SimpleValue(1, /* is_client_side_only = */ true)); |
| |
| EXPECT_TRUE(GetValues().at("identifier").is_client_side_only()); |
| } |
| |
| } // namespace autofill_assistant |