// 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/password_manager/ios/account_select_fill_data.h"

#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/common/password_form_fill_data.h"
#include "components/password_manager/ios/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

using autofill::PasswordFormFillData;
using password_manager::AccountSelectFillData;
using password_manager::FillData;
using password_manager::UsernameAndRealm;
using test_helpers::SetPasswordFormFillData;

namespace {
// Test data.
const char* kUrl = "http://example.com/";
const char* kFormNames[] = {"form_name1", "form_name2"};
const char* kUsernameElements[] = {"username1", "username2"};
const char* kUsernames[] = {"user0", "u_s_e_r"};
const char* kPasswordElements[] = {"password1", "password2"};
const char* kPasswords[] = {"password0", "secret"};
const char* kAdditionalUsernames[] = {"u$er2", nullptr};
const char* kAdditionalPasswords[] = {"secret", nullptr};

class AccountSelectFillDataTest : public PlatformTest {
 public:
  AccountSelectFillDataTest() {
    for (size_t i = 0; i < base::size(form_data_); ++i) {
      SetPasswordFormFillData(kUrl, kUrl, kUsernameElements[i], kUsernames[i],
                              kPasswordElements[i], kPasswords[i],
                              kAdditionalUsernames[i], kAdditionalPasswords[i],
                              false, &form_data_[i]);

      form_data_[i].name = base::ASCIIToUTF16(kFormNames[i]);
    }
  }

 protected:
  PasswordFormFillData form_data_[2];
};

TEST_F(AccountSelectFillDataTest, EmptyReset) {
  AccountSelectFillData account_select_fill_data;
  EXPECT_TRUE(account_select_fill_data.Empty());

  account_select_fill_data.Add(form_data_[0]);
  EXPECT_FALSE(account_select_fill_data.Empty());

  account_select_fill_data.Reset();
  EXPECT_TRUE(account_select_fill_data.Empty());
}

TEST_F(AccountSelectFillDataTest, IsSuggestionsAvailableOneForm) {
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);

  // Suggestions are avaialable for the correct form and field names.
  EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name, form_data_[0].username_field.unique_id, false));
  // Suggestion should be available to any password field.
  EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name, base::ASCIIToUTF16("password"), true));

  // Suggestions are not avaialable for different form name.
  EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name + base::ASCIIToUTF16("1"),
      form_data_[0].username_field.unique_id, false));
  EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name + base::ASCIIToUTF16("1"),
      form_data_[0].password_field.unique_id, true));

  // Suggestions are not avaialable for different field name.
  EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name,
      form_data_[0].username_field.unique_id + base::ASCIIToUTF16("1"), false));
}

TEST_F(AccountSelectFillDataTest, IsSuggestionsAvailableTwoForms) {
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);
  account_select_fill_data.Add(form_data_[1]);

  // Suggestions are avaialable for the correct form and field names.
  EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name, form_data_[0].username_field.unique_id, false));
  // Suggestions are avaialable for the correct form and field names.
  EXPECT_TRUE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[1].name, form_data_[1].username_field.unique_id, false));
  // Suggestions are not avaialable for different form name.
  EXPECT_FALSE(account_select_fill_data.IsSuggestionsAvailable(
      form_data_[0].name + base::ASCIIToUTF16("1"),
      form_data_[0].username_field.unique_id, false));
}

TEST_F(AccountSelectFillDataTest, RetrieveSuggestionsOneForm) {
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);

  for (bool is_password_field : {false, true}) {
    const base::string16 field_id =
        is_password_field ? form_data_[0].password_field.unique_id
                          : form_data_[0].username_field.unique_id;
    std::vector<UsernameAndRealm> suggestions =
        account_select_fill_data.RetrieveSuggestions(
            form_data_[0].name, field_id, is_password_field);
    EXPECT_EQ(2u, suggestions.size());
    EXPECT_EQ(base::ASCIIToUTF16(kUsernames[0]), suggestions[0].username);
    EXPECT_EQ(std::string(), suggestions[0].realm);
    EXPECT_EQ(base::ASCIIToUTF16(kAdditionalUsernames[0]),
              suggestions[1].username);
    EXPECT_EQ(std::string(), suggestions[1].realm);
  }
}

TEST_F(AccountSelectFillDataTest, RetrieveSuggestionsTwoForm) {
  // Test that after adding two PasswordFormFillData, suggestions for both forms
  // are shown, with credentials from the second PasswordFormFillData. That
  // emulates the case when credentials in the Password Store were changed
  // between load the first and the second forms.
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);
  account_select_fill_data.Add(form_data_[1]);

  std::vector<UsernameAndRealm> suggestions =
      account_select_fill_data.RetrieveSuggestions(
          form_data_[0].name, form_data_[0].username_field.unique_id, false);
  EXPECT_EQ(1u, suggestions.size());
  EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), suggestions[0].username);

  suggestions = account_select_fill_data.RetrieveSuggestions(
      form_data_[1].name, form_data_[1].username_field.unique_id, false);
  EXPECT_EQ(1u, suggestions.size());
  EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), suggestions[0].username);
}

TEST_F(AccountSelectFillDataTest, RetrievePSLMatchedSuggestions) {
  AccountSelectFillData account_select_fill_data;
  const char* kRealm = "http://a.example.com/";
  std::string kAdditionalRealm = "http://b.example.com/";

  // Make logins to be PSL matched by setting non-empy realm.
  form_data_[0].preferred_realm = kRealm;
  form_data_[0].additional_logins.begin()->second.realm = kAdditionalRealm;

  account_select_fill_data.Add(form_data_[0]);
  std::vector<UsernameAndRealm> suggestions =
      account_select_fill_data.RetrieveSuggestions(
          form_data_[0].name, form_data_[0].username_field.unique_id, false);
  EXPECT_EQ(2u, suggestions.size());
  EXPECT_EQ(base::ASCIIToUTF16(kUsernames[0]), suggestions[0].username);
  EXPECT_EQ(kRealm, suggestions[0].realm);
  EXPECT_EQ(base::ASCIIToUTF16(kAdditionalUsernames[0]),
            suggestions[1].username);
  EXPECT_EQ(kAdditionalRealm, suggestions[1].realm);
}

TEST_F(AccountSelectFillDataTest, GetFillData) {
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);
  account_select_fill_data.Add(form_data_[1]);

  for (bool is_password_field : {false, true}) {
    for (size_t form_i = 0; form_i < base::size(form_data_); ++form_i) {
      const auto& form_data = form_data_[form_i];
      // Suggestions should be shown on any password field on the form. So in
      // case of clicking on a password field it is taken an id different from
      // existing field ids.
      const base::string16 password_field =
          form_data.password_field.unique_id +
          (is_password_field ? base::ASCIIToUTF16("1") : base::string16());
      const base::string16 clicked_field =
          is_password_field ? password_field
                            : form_data.username_field.unique_id;

      // GetFillData() doesn't have form identifier in arguments, it should be
      // provided in RetrieveSuggestions().
      account_select_fill_data.RetrieveSuggestions(
          form_data.name, clicked_field, is_password_field);
      std::unique_ptr<FillData> fill_data =
          account_select_fill_data.GetFillData(
              base::ASCIIToUTF16(kUsernames[1]));

      ASSERT_TRUE(fill_data);
      EXPECT_EQ(form_data.origin, fill_data->origin);
      EXPECT_EQ(form_data.action, fill_data->action);
      EXPECT_EQ(base::ASCIIToUTF16(kUsernameElements[form_i]),
                fill_data->username_element);
      EXPECT_EQ(base::ASCIIToUTF16(kUsernames[1]), fill_data->username_value);
      EXPECT_EQ(password_field, fill_data->password_element);
      EXPECT_EQ(base::ASCIIToUTF16(kPasswords[1]), fill_data->password_value);
    }
  }
}

TEST_F(AccountSelectFillDataTest, GetFillDataOldCredentials) {
  AccountSelectFillData account_select_fill_data;
  account_select_fill_data.Add(form_data_[0]);
  account_select_fill_data.Add(form_data_[1]);

  // GetFillData() doesn't have form identifier in arguments, it should be
  // provided in RetrieveSuggestions().
  account_select_fill_data.RetrieveSuggestions(
      form_data_[0].name, form_data_[0].username_field.unique_id, false);

  // AccountSelectFillData should keep only last credentials. Check that in
  // request of old credentials nothing is returned.
  std::unique_ptr<FillData> fill_data =
      account_select_fill_data.GetFillData(base::ASCIIToUTF16(kUsernames[0]));
  EXPECT_FALSE(fill_data);
}

}  // namespace
