chromium/chrome/test/data/webui/chromeos/settings/internet_page/passpoint_subpage_test.ts

// Copyright 2023 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 {SettingsPasspointSubpageElement} from 'chrome://os-settings/lazy_load.js';
import {CrLinkRowElement, Router, routes} from 'chrome://os-settings/os_settings.js';
import {assert} from 'chrome://resources/ash/common/assert.js';
import {MojoConnectivityProvider} from 'chrome://resources/ash/common/connectivity/mojo_connectivity_provider.js';
import {PasspointSubscription} from 'chrome://resources/ash/common/connectivity/passpoint.mojom-webui.js';
import {MojoInterfaceProviderImpl} from 'chrome://resources/ash/common/network/mojo_interface_provider.js';
import {OncMojo} from 'chrome://resources/ash/common/network/onc_mojo.js';
import {AppType} from 'chrome://resources/cr_components/app_management/app_management.mojom-webui.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {CertificateType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/cros_network_config.mojom-webui.js';
import {NetworkType} from 'chrome://resources/mojo/chromeos/services/network_config/public/mojom/network_types.mojom-webui.js';
import {assertEquals, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {FakeNetworkConfig} from 'chrome://webui-test/chromeos/fake_network_config_mojom.js';
import {FakePasspointService} from 'chrome://webui-test/chromeos/fake_passpoint_service_mojom.js';
import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
import {eventToPromise} from 'chrome://webui-test/test_util.js';

import {FakePageHandler} from '../app_management/fake_page_handler.js';
import {setupFakeHandler} from '../app_management/test_util.js';

suite('PasspointSubpage', () => {
  let networkConfigApi_: FakeNetworkConfig;
  let passpointServiceApi_: FakePasspointService;
  let fakeHandler_: FakePageHandler;
  let passpointSubpage_: SettingsPasspointSubpageElement;

  const CA_HASH = 'CAHASH';
  const CA_PEM = 'test-pem';
  const CA_CN = 'Passpoint Example Certificate Authority';

  async function init(sub: PasspointSubscription) {
    const serverCas = [];
    serverCas.push({
      hash: CA_HASH,
      type: CertificateType.kServerCA,
      pemOrId: CA_PEM,
      issuedTo: CA_CN,
      issuedBy: CA_CN,
      availableForNetworkAuth: false,
      hardwareBacked: false,
      deviceWide: false,
    });
    networkConfigApi_.setCertificatesForTest(serverCas, []);
    passpointServiceApi_.addSubscription(sub);
    await flushTasks();

    const params = new URLSearchParams();
    params.append('id', sub.id);
    Router.getInstance().navigateTo(routes.PASSPOINT_DETAIL, params);
    await flushTasks();
  }

  function getListItems(id: string) {
    const div =
        passpointSubpage_.shadowRoot!.querySelector<HTMLDivElement>(`#${id}`);
    assertTrue(!!div);
    return div!.querySelectorAll('div.list-item');
  }

  function getExpirationDateItem(): HTMLDivElement|null {
    return passpointSubpage_.shadowRoot!.querySelector<HTMLDivElement>(
        '#passpointExpirationDate');
  }

  function getSourceText(): string {
    const div = passpointSubpage_.shadowRoot!.querySelector<HTMLDivElement>(
        '#passpointSourceText');
    assertTrue(!!div);
    return div!.textContent!.trim();
  }

  function getCertificateName(): string {
    const div = passpointSubpage_.shadowRoot!.querySelector<HTMLDivElement>(
        '#passpointCertificateName');
    assertTrue(!!div);
    return div!.textContent!.trim();
  }

  function getRemovalDialog(): HTMLDialogElement|null {
    return passpointSubpage_.shadowRoot!.querySelector<HTMLDialogElement>(
        '#removalDialog');
  }

  function getButton(id: string): HTMLButtonElement {
    const element =
        passpointSubpage_.shadowRoot!.querySelector<HTMLButtonElement>(
            `#${id}`);
    assertTrue(!!element);
    return element;
  }

  suiteSetup(() => {
    loadTimeData.overrideValues({
      isPasspointSettingsEnabled: true,
    });
    networkConfigApi_ = new FakeNetworkConfig();
    MojoInterfaceProviderImpl.getInstance().setMojoServiceRemoteForTest(
        networkConfigApi_);
    passpointServiceApi_ = new FakePasspointService();
    MojoConnectivityProvider.getInstance().setPasspointServiceForTest(
        passpointServiceApi_);
    fakeHandler_ = setupFakeHandler();
  });

  setup(async () => {
    passpointSubpage_ = document.createElement('settings-passpoint-subpage');
    assert(passpointSubpage_);

    networkConfigApi_.resetForTest();
    passpointServiceApi_.resetForTest();

    document.body.appendChild(passpointSubpage_);
    await waitAfterNextRender(passpointSubpage_);
  });

  teardown(() => {
    passpointSubpage_.remove();
    Router.getInstance().resetRouteForTesting();
  });

  test('Show Passpoint subscription', async () => {
    const sub = {
      id: 'sub-id',
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      trustedCa: CA_PEM,
      expirationEpochMs: 0n,
    };
    await init(sub);

    // No app registered, package name should be displayed.
    assertEquals(sub.provisioningSource, getSourceText());
    // Only one domain in the list.
    const list = getListItems('passpointDomainsList');
    assertEquals(1, list.length);
    // No expiration time.
    const item = getExpirationDateItem();
    assertEquals(null, item);
    // Certificate has a common name.
    assertEquals(CA_CN, getCertificateName());
  });

  test(
      'Show Passpoint subscription with domains and expiration time',
      async () => {
        // Create a date 7 days ahead of now.
        const date = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);

        const sub = {
          id: 'sub-id',
          domains: ['passpoint.example.com', 'passpoint2.example.com'],
          friendlyName: 'Passpoint Example Ltd.',
          provisioningSource: 'app.passpoint.example.com',
          trustedCa: CA_PEM,
          expirationEpochMs: BigInt(date.getTime()),
        };
        await init(sub);

        // No app registered, package name should be displayed.
        assertEquals(sub.provisioningSource, getSourceText());
        // Only one domain in the list.
        const list = getListItems('passpointDomainsList');
        assertEquals(2, list.length);
        // Expiration time is displayed.
        const item = getExpirationDateItem();
        assertTrue(!!item);
        // Certificate has a common name.
        assertEquals(CA_CN, getCertificateName());
      });

  test('Show Passpoint subscription without certificate', async () => {
    const sub = {
      id: 'sub-id',
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      expirationEpochMs: 0n,
      trustedCa: null,
    };
    await init(sub);

    // Certificate has a common name.
    assertEquals(
        passpointSubpage_.i18n('passpointSystemCALabel'), getCertificateName());
  });

  test('Show Passpoint subscription with app', async () => {
    const sub = {
      id: 'sub-id',
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      trustedCa: CA_PEM,
      expirationEpochMs: 0n,
    };
    const appTitle = 'My Passpoint App';
    const app = FakePageHandler.createApp('app_id', {
      type: AppType.kArc,
      title: appTitle,
      publisherId: sub.provisioningSource,
    });
    fakeHandler_!.setApps([app]);
    await init(sub);

    // Only one domain in the list.
    const list = getListItems('passpointDomainsList');
    assertEquals(1, list.length);
    // No expiration time.
    const item = getExpirationDateItem();
    assertEquals(null, item);
    // App name is displayed as subscription source.
    assertEquals(appTitle, getSourceText());
    // Certificate has a common name.
    assertEquals(CA_CN, getCertificateName());
  });

  test('Subscription removal', async () => {
    const subId = 'sub-id';
    const sub = {
      id: subId,
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      expirationEpochMs: 0n,
      trustedCa: null,
    };
    await init(sub);

    // Trigger a remove.
    const removeButton = getButton('removeButton');
    removeButton.click();
    await waitAfterNextRender(removeButton);
    let dialog = getRemovalDialog();
    assertTrue(!!dialog);

    // Check the dialog message contains the subscription name.
    const link = dialog!.querySelector('localized-link');
    assertTrue(!!link);
    const span = link!.shadowRoot!.querySelector('span');
    assertTrue(!!span);
    assertEquals(
        span.textContent,
        sub.friendlyName +
            ' will be removed from this device only. To make changes to your ' +
            'subscription, contact the subscription provider. ');

    // Cancel the dialog.
    const cancelButton = getButton('removalCancelButton');
    cancelButton.click();
    await waitAfterNextRender(cancelButton);
    dialog = getRemovalDialog();
    assertEquals(null, dialog);

    // Trigger a remove again.
    removeButton.click();
    await waitAfterNextRender(removeButton);
    dialog = getRemovalDialog();
    assertTrue(!!dialog);

    // Confirm the removal.
    const confirmButton = getButton('removalConfirmButton');
    confirmButton.click();
    await waitAfterNextRender(confirmButton);

    // Check the subscription is removed.
    dialog = getRemovalDialog();
    assertEquals(null, dialog);
    const resp = await passpointServiceApi_!.getPasspointSubscription(subId);
    assertEquals(null, resp.result);
  });

  test('Subscription with no associated networks', async () => {
    const subId = 'sub-id';
    const sub = {
      id: subId,
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      trustedCa: CA_PEM,
      expirationEpochMs: 0n,
    };
    networkConfigApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
    const wifi = OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi');
    networkConfigApi_.addNetworksForTest([wifi]);
    await init(sub);

    const elem = passpointSubpage_.shadowRoot!.querySelector<HTMLDivElement>(
        '#passpointNetworksList');
    assertNull(elem);
  });

  test('Subscription with multiple associated networks', async () => {
    const subId = 'sub-id';
    const sub = {
      id: subId,
      domains: ['passpoint.example.com'],
      friendlyName: 'Passpoint Example Ltd.',
      provisioningSource: 'app.passpoint.example.com',
      trustedCa: CA_PEM,
      expirationEpochMs: 0n,
    };
    networkConfigApi_.setNetworkTypeEnabledState(NetworkType.kWiFi, true);
    const wifi1 = OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi1');
    wifi1.typeState!.wifi!.passpointId = subId;
    const wifi2 = OncMojo.getDefaultNetworkState(NetworkType.kWiFi, 'wifi2');
    wifi2.typeState!.wifi!.passpointId = subId;
    networkConfigApi_.addNetworksForTest([
      wifi1,
      wifi2,
    ]);
    await init(sub);

    const list = getListItems('passpointNetworksList');
    assertEquals(2, list.length);
    const row = list[0]!.querySelector<CrLinkRowElement>('cr-link-row');
    assertTrue(!!row);

    const showDetailPromise = eventToPromise('show-detail', window);
    row!.click();
    const showDetailEvent = await showDetailPromise;
    assertEquals('wifi1_guid', showDetailEvent.detail.guid);
  });
});