| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <map> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/feature_list.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/no_destructor.h" |
| #include "base/path_service.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "components/autofill/core/browser/autofill_test_utils.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/autofill/core/browser/form_data_importer.h" |
| #include "components/autofill/core/browser/form_structure.h" |
| #include "components/autofill/core/browser/personal_data_manager.h" |
| #include "components/autofill/core/browser/test_autofill_client.h" |
| #include "components/autofill/core/common/autofill_features.h" |
| #include "components/autofill/core/common/form_data.h" |
| #include "testing/data_driven_testing/data_driven_test.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_APPLE) |
| #include "base/apple/foundation_util.h" |
| #endif |
| |
| namespace autofill { |
| |
| namespace { |
| const base::FilePath::CharType kFeatureName[] = FILE_PATH_LITERAL("autofill"); |
| const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("merge"); |
| const base::FilePath::CharType kFileNamePattern[] = FILE_PATH_LITERAL("*.in"); |
| |
| const char kFieldSeparator[] = ":"; |
| const char kProfileSeparator[] = "---"; |
| |
| const ServerFieldType kProfileFieldTypes[] = {NAME_FIRST, |
| NAME_MIDDLE, |
| NAME_LAST, |
| NAME_FULL, |
| EMAIL_ADDRESS, |
| COMPANY_NAME, |
| ADDRESS_HOME_STREET_ADDRESS, |
| ADDRESS_HOME_CITY, |
| ADDRESS_HOME_STATE, |
| ADDRESS_HOME_ZIP, |
| ADDRESS_HOME_COUNTRY, |
| PHONE_HOME_WHOLE_NUMBER}; |
| |
| const base::FilePath& GetTestDataDir() { |
| static base::NoDestructor<base::FilePath> dir([]() { |
| base::FilePath dir; |
| base::PathService::Get(base::DIR_SOURCE_ROOT, &dir); |
| return dir.AppendASCII("components") |
| .AppendASCII("test") |
| .AppendASCII("data"); |
| }()); |
| return *dir; |
| } |
| |
| const std::vector<base::FilePath> GetTestFiles() { |
| base::FilePath dir = GetTestDataDir(); |
| |
| dir = dir.AppendASCII("autofill").Append(kTestName).AppendASCII("input"); |
| base::FileEnumerator input_files(dir, false, base::FileEnumerator::FILES, |
| kFileNamePattern); |
| std::vector<base::FilePath> files; |
| for (base::FilePath input_file = input_files.Next(); !input_file.empty(); |
| input_file = input_files.Next()) { |
| files.push_back(input_file); |
| } |
| std::sort(files.begin(), files.end()); |
| |
| #if BUILDFLAG(IS_APPLE) |
| base::apple::ClearAmIBundledCache(); |
| #endif // BUILDFLAG(IS_APPLE) |
| |
| return files; |
| } |
| |
| // Serializes the |profiles| into a string. |
| std::string SerializeProfiles(const std::vector<AutofillProfile*>& profiles) { |
| std::string result; |
| for (auto* profile : profiles) { |
| result += kProfileSeparator; |
| result += "\n"; |
| for (const ServerFieldType& type : kProfileFieldTypes) { |
| std::u16string value = profile->GetRawInfo(type); |
| result += AutofillType::ServerFieldTypeToString(type); |
| result += kFieldSeparator; |
| if (!value.empty()) { |
| base::ReplaceFirstSubstringAfterOffset(&value, 0, u"\\n", u"\n"); |
| result += " "; |
| result += base::UTF16ToUTF8(value); |
| } |
| result += "\n"; |
| } |
| } |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| // A data-driven test for verifying merging of Autofill profiles. Each input is |
| // a structured dump of a set of implicitly detected autofill profiles. The |
| // corresponding output file is a dump of the saved profiles that result from |
| // importing the input profiles. The output file format is identical to the |
| // input format. |
| class AutofillMergeTest : public testing::DataDrivenTest, |
| public testing::TestWithParam<base::FilePath> { |
| public: |
| AutofillMergeTest(const AutofillMergeTest&) = delete; |
| AutofillMergeTest& operator=(const AutofillMergeTest&) = delete; |
| |
| protected: |
| AutofillMergeTest(); |
| ~AutofillMergeTest() override; |
| |
| // testing::Test: |
| void SetUp() override; |
| |
| void TearDown() override; |
| |
| // DataDrivenTest: |
| void GenerateResults(const std::string& input, std::string* output) override; |
| |
| // Deserializes a set of Autofill profiles from |profiles|, imports each |
| // sequentially, and fills |merged_profiles| with the serialized result. |
| void MergeProfiles(const std::string& profiles, std::string* merged_profiles); |
| |
| // Deserializes |str| into a field type. |
| ServerFieldType StringToFieldType(const std::string& str); |
| |
| base::test::TaskEnvironment task_environment_; |
| TestAutofillClient autofill_client_; |
| TestPersonalDataManager personal_data_; |
| std::unique_ptr<FormDataImporter> form_data_importer_; |
| |
| private: |
| std::map<std::string, ServerFieldType> string_to_field_type_map_; |
| }; |
| |
| AutofillMergeTest::AutofillMergeTest() |
| : testing::DataDrivenTest(GetTestDataDir(), kFeatureName, kTestName) { |
| for (size_t i = NO_SERVER_DATA; i < MAX_VALID_FIELD_TYPE; ++i) { |
| ServerFieldType field_type = ToSafeServerFieldType(i, MAX_VALID_FIELD_TYPE); |
| if (field_type == MAX_VALID_FIELD_TYPE) |
| continue; |
| string_to_field_type_map_[AutofillType::ServerFieldTypeToString( |
| field_type)] = field_type; |
| } |
| } |
| |
| AutofillMergeTest::~AutofillMergeTest() = default; |
| |
| void AutofillMergeTest::SetUp() { |
| test::DisableSystemServices(nullptr); |
| personal_data_.set_auto_accept_address_imports_for_testing(true); |
| form_data_importer_ = std::make_unique<FormDataImporter>( |
| &autofill_client_, |
| /*payments::PaymentsClient=*/nullptr, &personal_data_, "en"); |
| } |
| |
| void AutofillMergeTest::TearDown() { |
| test::ReenableSystemServices(); |
| } |
| |
| void AutofillMergeTest::GenerateResults(const std::string& input, |
| std::string* output) { |
| MergeProfiles(input, output); |
| } |
| |
| void AutofillMergeTest::MergeProfiles(const std::string& profiles, |
| std::string* merged_profiles) { |
| // Start with no saved profiles. |
| personal_data_.ClearAllLocalData(); |
| |
| // Create a test form. |
| FormData form; |
| form.name = u"MyTestForm"; |
| form.url = GURL("https://www.example.com/origin.html"); |
| form.action = GURL("https://www.example.com/action.html"); |
| |
| // Parse the input line by line. |
| std::vector<std::string> lines = base::SplitString( |
| profiles, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| for (size_t i = 0; i < lines.size(); ++i) { |
| std::string line = lines[i]; |
| if (line != kProfileSeparator) { |
| // Add a field to the current profile. |
| size_t separator_pos = line.find(kFieldSeparator); |
| ASSERT_NE(std::string::npos, separator_pos) |
| << "Wrong format for separator on line " << i; |
| std::u16string field_type = |
| base::UTF8ToUTF16(line.substr(0, separator_pos)); |
| do { |
| ++separator_pos; |
| } while (separator_pos < line.size() && line[separator_pos] == ' '); |
| std::u16string value = base::UTF8ToUTF16(line.substr(separator_pos)); |
| base::ReplaceFirstSubstringAfterOffset(&value, 0, u"\\n", u"\n"); |
| |
| FormFieldData field; |
| field.label = field_type; |
| field.name = field_type; |
| field.value = value; |
| field.form_control_type = "text"; |
| field.is_focusable = true; |
| form.fields.push_back(field); |
| } |
| |
| // The first line is always a profile separator, and the last profile is not |
| // followed by an explicit separator. |
| if ((i > 0 && line == kProfileSeparator) || i == lines.size() - 1) { |
| // Reached the end of a profile. Try to import it. |
| FormStructure form_structure(form); |
| for (size_t j = 0; j < form_structure.field_count(); ++j) { |
| // Set the heuristic type for each field, which is currently serialized |
| // into the field's name. |
| AutofillField* field = |
| const_cast<AutofillField*>(form_structure.field(j)); |
| ServerFieldType type = |
| StringToFieldType(base::UTF16ToUTF8(field->name)); |
| field->set_heuristic_type(GetActivePatternSource(), type); |
| } |
| |
| // Extract the profile. |
| auto extracted_data = form_data_importer_->ExtractFormData( |
| form_structure, |
| /*profile_autofill_enabled=*/true, |
| /*payment_methods_autofill_enabled=*/true); |
| form_data_importer_->ProcessAddressProfileImportCandidates( |
| extracted_data.address_profile_import_candidates, |
| /*allow_prompt=*/true); |
| EXPECT_FALSE(extracted_data.extracted_credit_card); |
| EXPECT_FALSE(extracted_data.extracted_upi_id.has_value()); |
| |
| // Clear the |form| to start a new profile. |
| form.fields.clear(); |
| } |
| } |
| |
| std::vector<AutofillProfile*> imported_profiles = |
| personal_data_.GetProfiles(); |
| // To ensure a consistent order with the output files, sort the profiles by |
| // modification date. This corresponds to the order in which the profiles |
| // were imported (or updated). |
| base::ranges::sort(imported_profiles, |
| [](AutofillProfile* a, AutofillProfile* b) { |
| return a->modification_date() < b->modification_date(); |
| }); |
| *merged_profiles = SerializeProfiles(imported_profiles); |
| } |
| |
| ServerFieldType AutofillMergeTest::StringToFieldType(const std::string& str) { |
| return string_to_field_type_map_[str]; |
| } |
| |
| TEST_P(AutofillMergeTest, DataDrivenMergeProfiles) { |
| const bool kIsExpectedToPass = true; |
| RunOneDataDrivenTest(GetParam(), GetOutputDirectory(), kIsExpectedToPass); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| AutofillMergeTest, |
| testing::ValuesIn(GetTestFiles())); |
| |
| } // namespace autofill |