/*
 * Tests methods that find specific logins in the store (findLogins,
 * searchLogins, and countLogins).
 *
 * The getAllLogins method is not tested explicitly here, because it is used by
 * all tests to verify additions, removals and modifications to the login store.
 */

"use strict";

// Globals

/**
 * Returns a list of new nsILoginInfo objects that are a subset of the test
 * data, built to match the specified query.
 *
 * @param aQuery
 *        Each property and value of this object restricts the search to those
 *        entries from the test data that match the property exactly.
 */
function buildExpectedLogins(aQuery)
{
  return TestData.loginList().filter(
    entry => Object.keys(aQuery).every(name => entry[name] === aQuery[name]));
}

/**
 * Tests the searchLogins function.
 *
 * @param aQuery
 *        Each property and value of this object is translated to an entry in
 *        the nsIPropertyBag parameter of searchLogins.
 * @param aExpectedCount
 *        Number of logins from the test data that should be found.  The actual
 *        list of logins is obtained using the buildExpectedLogins helper, and
 *        this value is just used to verify that modifications to the test data
 *        don't make the current test meaningless.
 */
function checkSearchLogins(aQuery, aExpectedCount)
{
  info("Testing searchLogins for " + JSON.stringify(aQuery));

  let expectedLogins = buildExpectedLogins(aQuery);
  Assert.equal(expectedLogins.length, aExpectedCount);

  let outCount = {};
  let logins = Services.logins.searchLogins(outCount, newPropertyBag(aQuery));
  Assert.equal(outCount.value, expectedLogins.length);
  LoginTestUtils.assertLoginListsEqual(logins, expectedLogins);
}

/**
 * Tests findLogins, searchLogins, and countLogins with the same query.
 *
 * @param aQuery
 *        The "hostname", "formSubmitURL", and "httpRealm" properties of this
 *        object are passed as parameters to findLogins and countLogins.  The
 *        same object is then passed to the checkSearchLogins function.
 * @param aExpectedCount
 *        Number of logins from the test data that should be found.  The actual
 *        list of logins is obtained using the buildExpectedLogins helper, and
 *        this value is just used to verify that modifications to the test data
 *        don't make the current test meaningless.
 */
function checkAllSearches(aQuery, aExpectedCount)
{
  info("Testing all search functions for " + JSON.stringify(aQuery));

  let expectedLogins = buildExpectedLogins(aQuery);
  Assert.equal(expectedLogins.length, aExpectedCount);

  // The findLogins and countLogins functions support wildcard matches by
  // specifying empty strings as parameters, while searchLogins requires
  // omitting the property entirely.
  let hostname = ("hostname" in aQuery) ? aQuery.hostname : "";
  let formSubmitURL = ("formSubmitURL" in aQuery) ? aQuery.formSubmitURL : "";
  let httpRealm = ("httpRealm" in aQuery) ? aQuery.httpRealm : "";

  // Test findLogins.
  let outCount = {};
  let logins = Services.logins.findLogins(outCount, hostname, formSubmitURL,
                                          httpRealm);
  Assert.equal(outCount.value, expectedLogins.length);
  LoginTestUtils.assertLoginListsEqual(logins, expectedLogins);

  // Test countLogins.
  let count = Services.logins.countLogins(hostname, formSubmitURL, httpRealm);
  Assert.equal(count, expectedLogins.length);

  // Test searchLogins.
  checkSearchLogins(aQuery, aExpectedCount);
}

// Tests

/**
 * Prepare data for the following tests.
 */
add_task(function test_initialize()
{
  for (let login of TestData.loginList()) {
    Services.logins.addLogin(login);
  }
});

/**
 * Tests findLogins, searchLogins, and countLogins with basic queries.
 */
add_task(function test_search_all_basic()
{
  // Find all logins, using no filters in the search functions.
  checkAllSearches({}, 23);

  // Find all form logins, then all authentication logins.
  checkAllSearches({ httpRealm: null }, 14);
  checkAllSearches({ formSubmitURL: null }, 9);

  // Find all form logins on one host, then all authentication logins.
  checkAllSearches({ hostname: "http://www4.example.com",
                     httpRealm: null }, 3);
  checkAllSearches({ hostname: "http://www2.example.org",
                     formSubmitURL: null }, 2);

  // Verify that scheme and subdomain are distinct in the hostname.
  checkAllSearches({ hostname: "http://www.example.com" }, 1);
  checkAllSearches({ hostname: "https://www.example.com" }, 1);
  checkAllSearches({ hostname: "https://example.com" }, 1);
  checkAllSearches({ hostname: "http://www3.example.com" }, 3);

  // Verify that scheme and subdomain are distinct in formSubmitURL.
  checkAllSearches({ formSubmitURL: "http://www.example.com" }, 2);
  checkAllSearches({ formSubmitURL: "https://www.example.com" }, 2);
  checkAllSearches({ formSubmitURL: "http://example.com" }, 1);

  // Find by formSubmitURL on a single host.
  checkAllSearches({ hostname: "http://www3.example.com",
                     formSubmitURL: "http://www.example.com" }, 1);
  checkAllSearches({ hostname: "http://www3.example.com",
                     formSubmitURL: "https://www.example.com" }, 1);
  checkAllSearches({ hostname: "http://www3.example.com",
                     formSubmitURL: "http://example.com" }, 1);

  // Find by httpRealm on all hosts.
  checkAllSearches({ httpRealm: "The HTTP Realm" }, 3);
  checkAllSearches({ httpRealm: "ftp://ftp.example.org" }, 1);
  checkAllSearches({ httpRealm: "The HTTP Realm Other" }, 2);

  // Find by httpRealm on a single host.
  checkAllSearches({ hostname: "http://example.net",
                     httpRealm: "The HTTP Realm" }, 1);
  checkAllSearches({ hostname: "http://example.net",
                     httpRealm: "The HTTP Realm Other" }, 1);
  checkAllSearches({ hostname: "ftp://example.net",
                     httpRealm: "ftp://example.net" }, 1);
});

/**
 * Tests searchLogins with advanced queries.
 */
add_task(function test_searchLogins()
{
  checkSearchLogins({ usernameField: "form_field_username" }, 12);
  checkSearchLogins({ passwordField: "form_field_password" }, 13);

  // Find all logins with an empty usernameField, including for authentication.
  checkSearchLogins({ usernameField: "" }, 11);

  // Find form logins with an empty usernameField.
  checkSearchLogins({ httpRealm: null,
                      usernameField: "" }, 2);

  // Find logins with an empty usernameField on one host.
  checkSearchLogins({ hostname: "http://www6.example.com",
                      usernameField: "" }, 1);
});

/**
 * Tests searchLogins with invalid arguments.
 */
add_task(function test_searchLogins_invalid()
{
  Assert.throws(() => Services.logins.searchLogins({},
                                      newPropertyBag({ username: "value" })),
                /Unexpected field/);
});

/**
 * Tests that matches are case-sensitive, compare the full field value, and are
 * strict when interpreting the prePath of URIs.
 */
add_task(function test_search_all_full_case_sensitive()
{
  checkAllSearches({ hostname: "http://www.example.com" }, 1);
  checkAllSearches({ hostname: "http://www.example.com/" }, 0);
  checkAllSearches({ hostname: "example.com" }, 0);

  checkAllSearches({ formSubmitURL: "http://www.example.com" }, 2);
  checkAllSearches({ formSubmitURL: "http://www.example.com/" }, 0);
  checkAllSearches({ formSubmitURL: "http://" }, 0);
  checkAllSearches({ formSubmitURL: "example.com" }, 0);

  checkAllSearches({ httpRealm: "The HTTP Realm" }, 3);
  checkAllSearches({ httpRealm: "The http Realm" }, 0);
  checkAllSearches({ httpRealm: "The HTTP" }, 0);
  checkAllSearches({ httpRealm: "Realm" }, 0);
});

/**
 * Tests findLogins, searchLogins, and countLogins with queries that should
 * return no values.
 */
add_task(function test_search_all_empty()
{
  checkAllSearches({ hostname: "http://nonexistent.example.com" }, 0);
  checkAllSearches({ formSubmitURL: "http://www.example.com",
                     httpRealm: "The HTTP Realm" }, 0);

  checkSearchLogins({ hostname: "" }, 0);
  checkSearchLogins({ id: "1000" }, 0);
});
