chromium/third_party/blink/web_tests/external/wpt/credential-management/support/otpcredential-helper.js

// These tests rely on the User Agent providing an implementation of
// MockWebOTPService.
//
// In Chromium-based browsers this implementation is provided by a polyfill
// in order to reduce the amount of test-only code shipped to users. To enable
// these tests the browser must be run with these options:
// //   --enable-blink-features=MojoJS,MojoJSTest

import {isChromiumBased} from '/resources/test-only-api.m.js';

/**
 * This enumeration is used by WebOTP WPTs to control mock backend behavior.
 * See MockWebOTPService below.
 */
export const Status = {
  SUCCESS: 0,
  UNHANDLED_REQUEST: 1,
  CANCELLED: 2,
  ABORTED: 3,
};

/**
 * A interface which must be implemented by browsers to support WebOTP WPTs.
 */
export class MockWebOTPService {
  /**
   * Accepts a function to be invoked in response to the next OTP request
   * received by the mock. The (optionally async) function, when executed, must
   * return an object with a `status` field holding one of the `Status` values
   * defined above, and -- if successful -- an `otp` field containing a
   * simulated OTP string.
   *
   * Tests will call this method directly to inject specific response behavior
   * into the browser-specific mock implementation.
   */
  async handleNextOTPRequest(responseFunc) {}
}

/**
 * Returns a Promise resolving to a browser-specific MockWebOTPService subclass
 * instance if one is available.
 */
async function createBrowserSpecificMockImpl() {
  if (isChromiumBased) {
    return await createChromiumMockImpl();
  }
  throw new Error('Unsupported browser.');
}

const asyncMock = createBrowserSpecificMockImpl();

export function expectOTPRequest() {
  return {
    async andReturn(callback) {
      const mock = await asyncMock;
      mock.handleNextOTPRequest(callback);
    }
  }
}

/**
 * Instantiates a Chromium-specific subclass of MockWebOTPService.
 */
async function createChromiumMockImpl() {
  const {SmsStatus, WebOTPService, WebOTPServiceReceiver} = await import(
      '/gen/third_party/blink/public/mojom/sms/webotp_service.mojom.m.js');
  const MockWebOTPServiceChromium = class extends MockWebOTPService {
    constructor() {
      super();
      this.mojoReceiver_ = new WebOTPServiceReceiver(this);
      this.interceptor_ =
          new MojoInterfaceInterceptor(WebOTPService.$interfaceName);
      this.interceptor_.oninterfacerequest = (e) => {
        this.mojoReceiver_.$.bindHandle(e.handle);
      };
      this.interceptor_.start();
      this.requestHandlers_ = [];
      Object.freeze(this);
    }

    handleNextOTPRequest(responseFunc) {
      this.requestHandlers_.push(responseFunc);
    }

    async receive() {
      if (this.requestHandlers_.length == 0) {
        throw new Error('Mock received unexpected OTP request.');
      }

      const responseFunc = this.requestHandlers_.shift();
      const response = await responseFunc();
      switch (response.status) {
        case Status.SUCCESS:
          if (typeof response.otp != 'string') {
            throw new Error('Mock success results require an OTP string.');
          }
          return {status: SmsStatus.kSuccess, otp: response.otp};
        case Status.UNHANDLED_REQUEST:
          return {status: SmsStatus.kUnhandledRequest};
        case Status.CANCELLED:
          return {status: SmsStatus.kCancelled};
        case Status.ABORTED:
          return {status: SmsStatus.kAborted};
        default:
          throw new Error(
              `Mock result contains unknown status: ${response.status}`);
      }
    }

    async abort() {}
  };
  return new MockWebOTPServiceChromium();
}