chromium/chrome/test/data/webui/chromeos/settings/kerberos_page/kerberos_accounts_subpage_test.ts

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'chrome://os-settings/lazy_load.js';

import {KerberosAccountsBrowserProxyImpl, SettingsKerberosAccountsSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {CrButtonElement, createRouterForTesting, CrToastElement, Router, routes} from 'chrome://os-settings/os_settings.js';
import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {getDeepActiveElement} from 'chrome://resources/js/util.js';
import {DomRepeat, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';

import {AccountIndex, TEST_KERBEROS_ACCOUNTS, TestKerberosAccountsBrowserProxy} from './test_kerberos_accounts_browser_proxy.js';

// Tests for the Kerberos Accounts settings subpage.
suite('<settings-kerberos-accounts-subpage>', () => {
  let browserProxy: TestKerberosAccountsBrowserProxy;
  let kerberosAccounts: SettingsKerberosAccountsSubpageElement;
  let accountList: DomRepeat;

  // Indices of 'More Actions' buttons.
  const MoreActions = {
    REFRESH_NOW: 0,
    SET_AS_ACTIVE_ACCOUNT: 1,
    REMOVE_ACCOUNT: 2,
  };

  suiteSetup(() => {
    // Reinitialize Router and routes based on load time data
    loadTimeData.overrideValues({isKerberosEnabled: true});

    const testRouter = createRouterForTesting();
    Router.resetInstanceForTesting(testRouter);
  });

  setup(() => {
    browserProxy = new TestKerberosAccountsBrowserProxy();
    KerberosAccountsBrowserProxyImpl.setInstanceForTesting(browserProxy);

    createDialog();
  });

  teardown(() => {
    kerberosAccounts.remove();
  });

  function createDialog() {
    if (kerberosAccounts) {
      kerberosAccounts.remove();
    }

    kerberosAccounts =
        document.createElement('settings-kerberos-accounts-subpage');
    document.body.appendChild(kerberosAccounts);
    flush();

    const list =
        kerberosAccounts.shadowRoot!.querySelector<DomRepeat>('#account-list');
    assertTrue(!!list);
    accountList = list;
  }

  function clickMoreActions(accountIndex: number, moreActionsIndex: number) {
    // Click 'More actions' for the given account.
    const crButton =
        kerberosAccounts.shadowRoot!.querySelectorAll<CrButtonElement>(
            '.more-actions')[accountIndex];
    assertTrue(!!crButton);
    crButton.click();
    // Click on the given action.
    const menu = kerberosAccounts.shadowRoot!.querySelector('cr-action-menu');
    assertTrue(!!menu);
    const button =
        menu.querySelectorAll<HTMLButtonElement>('button')[moreActionsIndex];
    assertTrue(!!button);
    button.click();
  }

  // Can be used to wait for event |eventName| on |element|, e.g.
  //   await onEvent(addDialog, 'close');
  function onEvent(element: HTMLElement, eventName: string): Promise<void> {
    return new Promise((resolve) => {
      element.addEventListener(eventName, function callback() {
        element.removeEventListener(eventName, callback);
        resolve();
      });
    });
  }

  test('Account list is populated at startup', async () => {
    await browserProxy.whenCalled('getAccounts');
    flush();
    // The test accounts were added in |getAccounts()| mock above.
    const items = accountList.items;
    assertTrue(!!items);
    assertEquals(TEST_KERBEROS_ACCOUNTS.length, items.length);
  });

  test('Account list signed-in signed-out labels', async () => {
    await browserProxy.whenCalled('getAccounts');
    flush();
    const accountListItem: NodeListOf<HTMLElement> =
        kerberosAccounts.shadowRoot!.querySelectorAll('.account-list-item');
    assertEquals(TEST_KERBEROS_ACCOUNTS.length, accountListItem.length);

    // Show 'Valid for <duration>' for accounts that are signed in.
    let signedIn = accountListItem[0]!.querySelector<HTMLElement>('.signed-in');
    let signedOut =
        accountListItem[0]!.querySelector<HTMLElement>('.signed-out');
    let testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.FIRST];
    assertTrue(!!signedIn);
    assertTrue(!!signedOut);
    assertTrue(!!testAccount);
    assertTrue(testAccount.isSignedIn);
    assertFalse(signedIn.hidden);
    assertTrue(signedOut.hidden);
    assertEquals(
        `Valid for ${testAccount.validForDuration}`, signedIn.innerText);

    // Show 'Expired' for accounts that are not signed in.
    signedIn = accountListItem[1]!.querySelector<HTMLElement>('.signed-in');
    signedOut = accountListItem[1]!.querySelector<HTMLElement>('.signed-out');
    testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.SECOND];
    assertTrue(!!signedIn);
    assertTrue(!!signedOut);
    assertTrue(!!testAccount);
    assertFalse(testAccount.isSignedIn);
    assertTrue(signedIn.hidden);
    assertFalse(signedOut.hidden);
    assertEquals('Expired', signedOut.innerText);
  });

  test('Add account', () => {
    // The kerberos-add-account-dialog shouldn't be open yet.
    assertNull(kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog'));

    const button = kerberosAccounts.shadowRoot!.querySelector<CrButtonElement>(
        '#add-account-button');
    assertTrue(!!button);
    button.click();
    flush();

    const addDialog = kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog');
    assertTrue(!!addDialog);
    assertEquals('', addDialog.$.username.value);
  });

  test('Reauthenticate account', async () => {
    // Wait until accounts are loaded.
    await browserProxy.whenCalled('getAccounts');
    flush();

    // The kerberos-add-account-dialog shouldn't be open yet.
    assertNull(kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog'));

    // Click "Sign-In" on an existing account.
    // Note that both accounts have a reauth button, but the first one is
    // hidden, so click the second one (clicking a hidden button works, but
    // it feels weird).
    const button =
        kerberosAccounts.shadowRoot!.querySelectorAll<CrButtonElement>(
            '.reauth-button')[AccountIndex.SECOND];
    assertTrue(!!button);
    button.click();
    flush();

    // Now the kerberos-add-account-dialog should be open with preset
    // username.
    const addDialog = kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog');
    assertTrue(!!addDialog);
    const testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.SECOND];
    assertTrue(!!testAccount);
    assertEquals(testAccount.principalName, addDialog.$.username.value);
  });

  // Appending '?kerberos_reauth=<principal>' to the URL opens the reauth
  // dialog for that account.
  test('Handle reauth query parameter', async () => {
    const testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.FIRST];
    assertTrue(!!testAccount);
    const principal_name = testAccount.principalName;
    const params = new URLSearchParams();
    params.append('kerberos_reauth', principal_name);
    Router.getInstance().navigateTo(routes.KERBEROS_ACCOUNTS_V2, params);

    // The flushTasks is necessary since the kerberos_reauth param would
    // otherwise be handled AFTER the callback below is executed.
    await browserProxy.whenCalled('getAccounts');
    await flushTasks();
    flush();
    const addDialog = kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog');
    assertTrue(!!addDialog);
    assertEquals(principal_name, addDialog.$.username.value);
  });

  test('Refresh now', async () => {
    await browserProxy.whenCalled('getAccounts');
    flush();
    clickMoreActions(AccountIndex.FIRST, MoreActions.REFRESH_NOW);
    flush();

    const addDialog = kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog');
    assertTrue(!!addDialog);
    const testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.FIRST];
    assertTrue(!!testAccount);
    assertEquals(testAccount.principalName, addDialog.$.username.value);
  });

  test('Refresh account shows toast', async () => {
    let toast = kerberosAccounts.shadowRoot!.querySelector<CrToastElement>(
        '#account-toast');
    assertTrue(!!toast);
    assertFalse(toast.open);

    await browserProxy.whenCalled('getAccounts');
    flush();
    clickMoreActions(AccountIndex.FIRST, MoreActions.REFRESH_NOW);
    flush();

    const addDialog = kerberosAccounts.shadowRoot!.querySelector(
        'kerberos-add-account-dialog');
    assertTrue(!!addDialog);
    const button =
        addDialog.shadowRoot!.querySelector<CrButtonElement>('.action-button');
    assertTrue(!!button);
    button.click();
    flush();

    await onEvent(addDialog, 'close');
    await flushTasks();
    flush();
    toast = kerberosAccounts.shadowRoot!.querySelector<CrToastElement>(
        '#account-toast');
    assertTrue(!!toast);
    assertTrue(toast.open);
    const toastLabel =
        kerberosAccounts.shadowRoot!.querySelector('#account-toast-label');
    assertTrue(!!toastLabel);
    assertTrue(toastLabel.innerHTML.includes('refreshed'));
  });

  test('Remove account', async () => {
    await browserProxy.whenCalled('getAccounts');
    flush();
    clickMoreActions(AccountIndex.FIRST, MoreActions.REMOVE_ACCOUNT);
    const account = await browserProxy.whenCalled('removeAccount');
    const testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.FIRST];
    assertTrue(!!testAccount);
    assertEquals(testAccount.principalName, account.principalName);
  });

  test('Deep link to remove account dropdown', async () => {
    const params = new URLSearchParams();
    params.append('settingId', '1801');
    Router.getInstance().navigateTo(routes.KERBEROS_ACCOUNTS_V2, params);

    await browserProxy.whenCalled('getAccounts');
    await flushTasks();
    flush();

    const deepLinkElement =
        kerberosAccounts.shadowRoot!.querySelectorAll('cr-icon-button')[0];
    assertTrue(!!deepLinkElement);
    await waitAfterNextRender(deepLinkElement);
    assertEquals(
        deepLinkElement, getDeepActiveElement(),
        'Kebab menu should be focused for settingId=1801.');
  });

  test('Remove account shows toast', async () => {
    const toast = kerberosAccounts.shadowRoot!.querySelector<CrToastElement>(
        '#account-toast');
    assertTrue(!!toast);
    assertFalse(toast.open);

    await browserProxy.whenCalled('getAccounts');
    flush();
    clickMoreActions(AccountIndex.FIRST, MoreActions.REMOVE_ACCOUNT);
    await browserProxy.whenCalled('removeAccount');
    await flushTasks();
    flush();
    assertTrue(toast.open);
    const toastLabel =
        kerberosAccounts.shadowRoot!.querySelector('#account-toast-label');
    assertTrue(!!toastLabel);
    assertTrue(toastLabel.innerHTML.includes('removed'));
  });

  test('Account list is updated when Kerberos accounts updates', () => {
    assertEquals(1, browserProxy.getCallCount('getAccounts'));
    webUIListenerCallback('kerberos-accounts-changed');
    assertEquals(2, browserProxy.getCallCount('getAccounts'));
  });

  test('Set as active account', async () => {
    await browserProxy.whenCalled('getAccounts');
    flush();
    clickMoreActions(AccountIndex.SECOND, MoreActions.SET_AS_ACTIVE_ACCOUNT);
    const account = await browserProxy.whenCalled('setAsActiveAccount');
    const testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.SECOND];
    assertTrue(!!testAccount);
    assertEquals(testAccount.principalName, account.principalName);
  });

  test('Show policy indicator for managed accounts', async () => {
    // Make sure we have at least one managed and one unmanaged account.
    let testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.FIRST];
    assertTrue(!!testAccount);
    assertFalse(testAccount.isManaged);
    testAccount = TEST_KERBEROS_ACCOUNTS[AccountIndex.THIRD];
    assertTrue(!!testAccount);
    assertTrue(testAccount.isManaged);

    await browserProxy.whenCalled('getAccounts');
    flush();
    const accountListItem: NodeListOf<HTMLElement> =
        kerberosAccounts.shadowRoot!.querySelectorAll('.account-list-item');
    assertEquals(TEST_KERBEROS_ACCOUNTS.length, accountListItem.length);

    for (let i = 0; i < TEST_KERBEROS_ACCOUNTS.length; i++) {
      // Assert account has policy indicator iff account is managed.
      const hasAccountPolicyIndicator =
          !!accountListItem[i]!.querySelector('.account-policy-indicator');
      assertEquals(
          TEST_KERBEROS_ACCOUNTS[i]!.isManaged, hasAccountPolicyIndicator);

      // Assert 'Remove' button is disabled iff account is managed.
      const button =
          accountListItem[i]!.querySelector<CrButtonElement>('.more-actions');
      assertTrue(!!button);
      button.click();
      let menu = kerberosAccounts.shadowRoot!.querySelector('cr-action-menu');
      assertTrue(!!menu);
      const moreActions = menu.querySelectorAll('button');
      assertTrue(!!moreActions);
      const removeAccountButton = moreActions[MoreActions.REMOVE_ACCOUNT];
      assertTrue(!!removeAccountButton);
      assertEquals(
          TEST_KERBEROS_ACCOUNTS[i]!.isManaged, removeAccountButton.disabled);

      // Assert 'Remove' button has policy indicator iff account is managed.
      flush();
      const hasRemovalPolicyIndicator = !!removeAccountButton.querySelector(
          '#remove-account-policy-indicator');
      assertEquals(
          TEST_KERBEROS_ACCOUNTS[i]!.isManaged, hasRemovalPolicyIndicator);

      menu = kerberosAccounts.shadowRoot!.querySelector('cr-action-menu');
      assertTrue(!!menu);
      menu.close();
    }
  });

  test('Add accounts allowed', () => {
    assertTrue(loadTimeData.getBoolean('kerberosAddAccountsAllowed'));
    createDialog();

    assertNull(kerberosAccounts.shadowRoot!.querySelector(
        '#add-account-policy-indicator'));
    const button = kerberosAccounts.shadowRoot!.querySelector<CrButtonElement>(
        '#add-account-button');
    assertTrue(!!button);
    assertFalse(button.disabled);
  });

  test('Add accounts not allowed', () => {
    loadTimeData.overrideValues({kerberosAddAccountsAllowed: false});
    createDialog();

    assertTrue(!!kerberosAccounts.shadowRoot!.querySelector(
        '#add-account-policy-indicator'));
    const button = kerberosAccounts.shadowRoot!.querySelector<CrButtonElement>(
        '#add-account-button');
    assertTrue(!!button);
    assertTrue(button.disabled);
  });
});