<!DOCTYPE html>
<title>Federated Credential Management API network request tests.</title>
<link rel="help" href="https://fedidcg.github.io/FedCM">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>

<body>

<script type="module">
import {request_options_with_mediation_required,
        alt_request_options_with_mediation_required,
        request_options_with_mediation_optional,
        fedcm_test,
        select_manifest,
        set_fedcm_cookie} from './support/fedcm-helper.sub.js';

function loadUrlInIframe(url) {
  let iframe = document.createElement("iframe");
  return new Promise(resolve => {
    iframe.src = url;
    iframe.onload = function() { resolve(iframe); };
    document.body.appendChild(iframe);
  });
}

fedcm_test(async t => {
  const cred = await navigator.credentials.get(request_options_with_mediation_required());
  assert_equals(cred.token, "token");
}, "Successfully obtaining token should resolve the promise.");

fedcm_test(async t => {
  const first = navigator.credentials.get(request_options_with_mediation_required());
  const second = navigator.credentials.get(alt_request_options_with_mediation_required());

  // We have to call promise_rejects_dom here, because if we call it after
  // the promise gets rejected, the unhandled rejection event handler is called
  // and fails the test even if we handle the rejection later.
  const rej = promise_rejects_dom(t, 'AbortError', second);

  const first_cred = await first;
  assert_equals(first_cred.token, "token");

  return rej;
},
"When there's a pending request, a second `get` call should be rejected. ");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  test_options.identity.providers = [];
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_js(t, TypeError, cred);
}, "Reject when provider list is empty");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  delete test_options.identity.providers[0].configURL;
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_js(t, TypeError, cred);
}, "Reject when configURL is missing" );

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  test_options.identity.providers[0].configURL = 'test';
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_dom(t, "InvalidStateError", cred);
}, "Reject when configURL is invalid");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  test_options.identity.providers[0].clientId = '';
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_dom(t, "InvalidStateError", cred);
}, "Reject when clientId is empty");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  assert_true("nonce" in test_options.identity.providers[0]);
  delete test_options.identity.providers[0].nonce;
  const cred = await navigator.credentials.get(test_options);
  assert_equals(cred.token, "token");
}, "nonce is not required in FederatedIdentityProvider.");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required();
  delete test_options.identity.providers[0].clientId;
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_js(t, TypeError, cred);
}, "Reject when clientId is missing" );

fedcm_test(async t => {
  let controller = new AbortController();
  let test_options = request_options_with_mediation_required();
  test_options.signal = controller.signal;
  const cred = navigator.credentials.get(test_options);
  controller.abort();
  return promise_rejects_dom(t, 'AbortError', cred);
}, "Test the abort signal");

fedcm_test(async t => {
  let controller = new AbortController();
  let test_options = request_options_with_mediation_required();
  test_options.signal = controller.signal;
  const first_cred = navigator.credentials.get(test_options);
  controller.abort();
  await promise_rejects_dom(t, 'AbortError', first_cred);

  const second_cred = await navigator.credentials.get(request_options_with_mediation_required());
  assert_equals(second_cred.token, "token");
}, "Get after abort should work");

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required('manifest-not-in-list.json');
  const cred = navigator.credentials.get(test_options);
  return promise_rejects_dom(t, 'NetworkError', cred);
}, 'Test that the promise is rejected if the manifest is not in the manifest list');

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required("manifest_redirect_accounts.json");
  await select_manifest(t, test_options);

  const cred = navigator.credentials.get(test_options);
  return promise_rejects_dom(t, 'NetworkError', cred);
}, 'Test that promise is rejected if accounts endpoint redirects');
// A malicious site might impersonate an IDP, redirecting the accounts endpoint to a
// legitimate IDP in order to get the list of user accounts.

fedcm_test(async t => {
  let test_options = request_options_with_mediation_required("manifest_redirect_token.json");
  await select_manifest(t, test_options);

  const cred = navigator.credentials.get(test_options);
  return promise_rejects_dom(t, 'NetworkError', cred);
}, 'Test that token endpoint does not follow redirects');
// The token endpoint should not follow redirects because the user has not consented
// to share their identity with the redirect destination.

fedcm_test(async t => {
  // Reset the client_metadata fetch count.
  const clear_metadata_count_path = `support/fedcm/client_metadata_clear_count.py`;
  await fetch(clear_metadata_count_path);

  const cred = await navigator.credentials.get(request_options_with_mediation_required());
  assert_equals(cred.token, "token");

  await new Promise(resolve => {
    let popup_window = window.open('support/fedcm/client_metadata.py?skip_checks=1');
    const popup_window_load_handler = (event) => {
      popup_window.removeEventListener('load', popup_window_load_handler);
      popup_window.close();
      resolve();
    };
    popup_window.addEventListener('load', popup_window_load_handler);
  });

  const client_metadata_counter = await fetch(clear_metadata_count_path);
  const client_metadata_counter_text = await client_metadata_counter.text()
  assert_equals(client_metadata_counter_text, "2");
}, 'Test client_metadata request');
// Test:
// - Headers sent to client metadata endpoint. (Counter is not incremented if the headers are
//   wrong.)
// - That the client metadata response is not cached. If the client metadata response were
// cached, when the user visits the IDP as a first party, the IDP would be able to determine the
// last RP the user visited regardless of whether the user granted consent via the FedCM prompt.

fedcm_test(async t => {
  const service_worker_url = 'support/fedcm/intercept_service_worker.js';
  const sw_scope_url = '/credential-management/support/fedcm/';
  // URL for querying number of page loads observed by service worker.
  const query_sw_intercepts_url = 'support/fedcm/query_service_worker_intercepts.html';
  const page_in_sw_scope_url = 'support/fedcm/simple.html';

  const sw_registration = await service_worker_unregister_and_register(
      t, service_worker_url, sw_scope_url);
  t.add_cleanup(() => service_worker_unregister(t, sw_scope_url));
  await wait_for_state(t, sw_registration.installing, 'activated');

  // Verify that service worker works.
  await loadUrlInIframe(page_in_sw_scope_url);
  let query_sw_iframe = await loadUrlInIframe(query_sw_intercepts_url);
  assert_equals(query_sw_iframe.contentDocument.body.textContent, "1");

  await set_fedcm_cookie();
  const cred = await navigator.credentials.get(request_options_with_mediation_required());
  assert_equals(cred.token, "token");

  // Use cache buster query parameter to avoid cached response.
  let query_sw_iframe2 = await loadUrlInIframe(query_sw_intercepts_url + "?2");
  assert_equals(query_sw_iframe2.contentDocument.body.textContent, "1");
}, 'Test that service worker cannot observe fetches performed by FedCM API');

fedcm_test(async t => {
  let test_options = request_options_with_mediation_optional("manifest_with_single_account.json");
  await select_manifest(t, test_options);

  // Signs in john_doe so that they will be a returning user
  let cred = await navigator.credentials.get(test_options);
  assert_equals(cred.token, "account_id=john_doe");

  test_options = request_options_with_mediation_optional("manifest_with_two_accounts.json");
  await select_manifest(t, test_options);

  // There are two accounts "Jane" and "John" returned in that order. Without
  // auto re-authn, the first account "Jane" would be selected and an token
  // would be issued to that account. However, since "John" is returning and
  // "Jane" is a new user, the second account "John" will be selected.
  cred = await navigator.credentials.get(test_options);
  assert_equals(cred.token, "account_id=john_doe");
}, "Test that the returning account from the two accounts will be auto re-authenticated.");

</script>
