/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

// Creates address books in various configurations (current and legacy) and
// performs requests in each of them to prove that OAuth2 authentication is
// working as expected.

var { CardDAVDirectory } = ChromeUtils.import(
  "resource:///modules/CardDAVDirectory.jsm"
);
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

var LoginInfo = Components.Constructor(
  "@mozilla.org/login-manager/loginInfo;1",
  Ci.nsILoginInfo,
  "init"
);

// Ideal login info. This is what would be saved if you created a new calendar.
const ORIGIN = "oauth://mochi.test";
const SCOPE = "test_scope";
const USERNAME = "bob@test.invalid";
const VALID_TOKEN = "bobs_refresh_token";

const PATH = "comm/mailnews/addrbook/test/browser/data/";
const URL = `http://mochi.test:8888/browser/${PATH}`;

/**
 * Set a string pref for the given directory.
 *
 * @param {string} dirPrefId
 * @param {string} key
 * @param {string} value
 */
function setPref(dirPrefId, key, value) {
  Services.prefs.setStringPref(`ldap_2.servers.${dirPrefId}.${key}`, value);
}

/**
 * Clear any existing saved logins and add the given ones.
 *
 * @param {string[][]} - Zero or more arrays consisting of origin, realm,
 *   username, and password.
 */
function setLogins(...logins) {
  Services.logins.removeAllLogins();
  for (let [origin, realm, username, password] of logins) {
    Services.logins.addLogin(
      new LoginInfo(origin, null, realm, username, password, "", "")
    );
  }
}

/**
 * Create a directory with the given id, perform a request, and check that the
 * correct authorisation header was used. If the user is required to
 * re-authenticate with the provider, check that the new token is stored in the
 * right place.
 *
 * @param {string} dirPrefId - Pref ID of the new directory.
 * @param {string} uid - UID of the new directory.
 * @param {string} [newTokenUsername] - If given, re-authentication must happen
 *   and the new token stored with this user name.
 */
async function subtest(dirPrefId, uid, newTokenUsername) {
  let directory = new CardDAVDirectory();
  directory._dirPrefId = dirPrefId;
  directory._uid = uid;
  directory.__prefBranch = Services.prefs.getBranch(
    `ldap_2.servers.${dirPrefId}.`
  );
  directory.__prefBranch.setStringPref("carddav.url", URL);

  let response = await directory._makeRequest("auth_headers.sjs");
  Assert.equal(response.status, 200);
  let headers = JSON.parse(response.text);

  if (newTokenUsername) {
    Assert.equal(headers.authorization, "Bearer new_access_token");

    let logins = Services.logins
      .findLogins(ORIGIN, null, SCOPE)
      .filter(l => l.username == newTokenUsername);
    Assert.equal(logins.length, 1);
    Assert.equal(logins[0].username, newTokenUsername);
    Assert.equal(logins[0].password, "new_refresh_token");
  } else {
    Assert.equal(headers.authorization, "Bearer bobs_access_token");
  }

  Services.logins.removeAllLogins();
}

// Test making a request when there is no matching token stored.

/** No token stored, no username set. */
add_task(function testAddressBookOAuth_uid_none() {
  let dirPrefId = "uid_none";
  let uid = "testAddressBookOAuth_uid_none";
  return subtest(dirPrefId, uid, uid);
});

// Test making a request when there IS a matching token, but the server rejects
// it. Currently a new token is not requested on failure.

/** Expired token stored with UID. */
add_task(function testAddressBookOAuth_uid_expired() {
  let dirPrefId = "uid_expired";
  let uid = "testAddressBookOAuth_uid_expired";
  setLogins([ORIGIN, SCOPE, uid, "expired_token"]);
  return subtest(dirPrefId, uid, uid);
}).skip(); // Broken.

// Test making a request with a valid token.

/** Valid token stored with UID. This is the old way of storing the token. */
add_task(function testAddressBookOAuth_uid_valid() {
  let dirPrefId = "uid_valid";
  let uid = "testAddressBookOAuth_uid_valid";
  setLogins([ORIGIN, SCOPE, uid, VALID_TOKEN]);
  return subtest(dirPrefId, uid);
});

/** Valid token stored with username, exact scope. */
add_task(function testAddressBookOAuth_username_validSingle() {
  let dirPrefId = "username_validSingle";
  let uid = "testAddressBookOAuth_username_validSingle";
  setPref(dirPrefId, "carddav.username", USERNAME);
  setLogins(
    [ORIGIN, SCOPE, USERNAME, VALID_TOKEN],
    [ORIGIN, "other_scope", USERNAME, "other_refresh_token"]
  );
  return subtest(dirPrefId, uid);
});

/** Valid token stored with username, many scopes. */
add_task(function testAddressBookOAuth_username_validMultiple() {
  let dirPrefId = "username_validMultiple";
  let uid = "testAddressBookOAuth_username_validMultiple";
  setPref(dirPrefId, "carddav.username", USERNAME);
  setLogins([ORIGIN, "scope test_scope other_scope", USERNAME, VALID_TOKEN]);
  return subtest(dirPrefId, uid);
});
