Commit 7262d4d6 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

Migrate credential-management WPTs to JS modules

Update the credential-management WPTs to use JS modules, including for
Mojo JS bindings.

This also refactors the supporting modules to isolate individual
tests from any browser-specific details.

Bug: 1004256
Change-Id: If5428dbd4f371fdc92c62da2a2378e8221c5c658
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472201Reviewed-by: default avatarMichael Moss <mmoss@chromium.org>
Reviewed-by: default avatarRobert Ma <robertma@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#817715}
parent 5c8c187a
......@@ -326,6 +326,11 @@ FILES = [
'buildtype': ['dev', 'official'],
'archive': 'mojojs.zip',
},
{
'filename': 'gen/mojo/public/js/bindings.js',
'buildtype': ['dev', 'official'],
'archive': 'mojojs.zip',
},
{
# including *.mojom-lite.js, too
'filename': 'gen/mojo/public/mojom/base/*.mojom*.js',
......@@ -345,7 +350,7 @@ FILES = [
},
# MojoJS support data (credential-management):
{
'filename': 'gen/third_party/blink/public/mojom/sms/webotp_service.mojom-lite.js',
'filename': 'gen/third_party/blink/public/mojom/sms/webotp_service.mojom.m.js',
'buildtype': ['dev', 'official'],
'archive': 'mojojs.zip',
},
......
......@@ -3,15 +3,12 @@
<title>Tests OTPCredential</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/test-only-api.js"></script>
<script src="support/otpcredential-helper.js"></script>
<script>
'use strict';
<script type="module">
import {Status, expectOTPRequest} from './support/otpcredential-helper.js';
promise_test(async t => {
await expect(receive).andReturn(async () => {
return {status: Status.kSuccess, otp: "ABC"};
});
await expectOTPRequest().andReturn(
() => ({status: Status.SUCCESS, otp: "ABC"}));
let cred = await navigator.credentials.get({otp: {transport: ["sms"]}});
......@@ -19,17 +16,15 @@ promise_test(async t => {
}, 'Basic usage');
promise_test(async t => {
await expect(receive).andReturn(async () => {
return {status: Status.kSuccess, otp: "ABC"};
});
await expect(receive).andReturn(async () => {
return {status: Status.kSuccess, otp: "ABC2"};
});
await expectOTPRequest().andReturn(
() => ({status: Status.SUCCESS, otp: "ABC"}));
await expectOTPRequest().andReturn(
() => ({status: Status.SUCCESS, otp: "ABC2"}));
let sms1 = navigator.credentials.get({otp: {transport: ["sms"]}});
let sms2 = navigator.credentials.get({otp: {transport: ["sms"]}});
let cred2= await sms2;
let cred2 = await sms2;
let cred1 = await sms1;
assert_equals(cred1.code, "ABC");
......@@ -37,21 +32,18 @@ promise_test(async t => {
}, 'Handle multiple requests in different order.');
promise_test(async t => {
await expect(receive).andReturn(async () => {
return {status: Status.kCancelled};
});
await expect(receive).andReturn(async () => {
return {status: Status.kSuccess, otp: "success"};
});
await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
await expectOTPRequest().andReturn(
() => ({status: Status.SUCCESS, otp: "success"}));
let cancelled_sms = navigator.credentials.get({otp: {transport: ["sms"]}});
let successful_sms = navigator.credentials.get({otp: {transport: ["sms"]}});
let cancelledRequest = navigator.credentials.get({otp: {transport: ["sms"]}});
let successfulCred =
await navigator.credentials.get({otp: {transport: ["sms"]}});
let successful_cred = await successful_sms;
assert_equals(successful_cred.code, "success");
assert_equals(successfulCred.code, "success");
try {
await cancelled_sms;
await cancelledRequest;
assert_unreached('Expected AbortError to be thrown.');
} catch (error) {
assert_equals(error.name, "AbortError");
......@@ -59,9 +51,7 @@ promise_test(async t => {
}, 'Handle multiple requests with success and error.');
promise_test(async t => {
await expect(receive).andReturn(async () => {
return {status: Status.kCancelled};
});
await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
await promise_rejects_dom(t, 'AbortError', navigator.credentials.get(
{otp: {transport: ["sms"]}}));
......
'use strict';
// These tests rely on the User Agent providing an implementation of
// the sms retriever.
// 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
const Status = {};
async function loadChromiumResources() {
const resources = [
'/gen/mojo/public/mojom/base/time.mojom-lite.js',
'/gen/third_party/blink/public/mojom/sms/webotp_service.mojom-lite.js',
];
await loadMojoResources(resources, true);
await loadScript('/resources/chromium/mock-sms-receiver.js');
import {isChromiumBased} from '/resources/test-only-api.m.js';
Status.kSuccess = blink.mojom.SmsStatus.kSuccess;
Status.kTimeout = blink.mojom.SmsStatus.kTimeout;
Status.kCancelled = blink.mojom.SmsStatus.kCancelled;
/**
* 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,
};
async function create_sms_provider() {
if (typeof SmsProvider === 'undefined') {
if (isChromiumBased) {
await loadChromiumResources();
} else {
throw new Error('Mojo testing interface is not available.');
}
}
if (typeof SmsProvider === 'undefined') {
throw new Error('Failed to set up SmsProvider.');
}
return new SmsProvider();
/**
* 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) {}
}
function receive() {
throw new Error("expected to be overriden by tests");
/**
* 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.');
}
function expect(call) {
const asyncMock = createBrowserSpecificMockImpl();
export function expectOTPRequest() {
return {
async andReturn(callback) {
const mock = await create_sms_provider();
mock.pushReturnValuesForTesting(call.name, 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();
}
<!doctype html>
<script src="/resources/test-only-api.js"></script>
<script src="otpcredential-helper.js"></script>
<script>
'use strict';
<script type="module">
import {Status, expectOTPRequest} from './otpcredential-helper.js';
// Loading otpcredential-iframe.html in the test will make an OTPCredentials
// call on load, and trigger a postMessage upon completion.
......@@ -13,24 +11,16 @@
// string errorType: error.name
// }
// Intercept successful calls and return mocked value.
(async function() {
await expect(receive).andReturn(() => {
return Promise.resolve({
status: Status.kSuccess,
otp: "ABC123",
});
});
}());
window.onload = async () => {
try {
const credentials =
await navigator.credentials.get({otp: {transport: ["sms"]}});
window.parent.postMessage({result: "Pass", code: credentials.code}, '*');
} catch (error) {
window.parent.postMessage({result: "Fail", errorType: error.name}, '*');
}
try {
await expectOTPRequest().andReturn(
() => ({status: Status.SUCCESS, otp: "ABC123"}));
const credentials =
await navigator.credentials.get({otp: {transport: ["sms"]}});
window.parent.postMessage({result: "Pass", code: credentials.code}, '*');
} catch (error) {
window.parent.postMessage({result: "Fail", errorType: error.name}, '*');
}
}
</script>
'use strict';
const SmsProvider = (() => {
class MockWebOTPService {
constructor() {
this.mojoReceiver_ = new blink.mojom.WebOTPServiceReceiver(this);
this.interceptor_ =
new MojoInterfaceInterceptor(blink.mojom.WebOTPService.$interfaceName);
this.interceptor_.oninterfacerequest = (e) => {
this.mojoReceiver_.$.bindHandle(e.handle);
}
this.interceptor_.start();
this.returnValues_ = {};
}
async receive() {
let call = this.returnValues_.receive ?
this.returnValues_.receive.shift() : null;
if (!call)
return;
return call();
}
async abort() {};
pushReturnValuesForTesting(callName, value) {
this.returnValues_[callName] = this.returnValues_[callName] || [];
this.returnValues_[callName].push(value);
return this;
}
}
const mockWebOTPService = new MockWebOTPService();
class SmsProviderChromium {
constructor() {
Object.freeze(this); // Make it immutable.
}
pushReturnValuesForTesting(callName, callback) {
mockWebOTPService.pushReturnValuesForTesting(callName, callback);
}
}
return SmsProviderChromium;
})();
/* Whether the browser is Chromium-based with MojoJS enabled */
export const isChromiumBased = 'MojoInterfaceInterceptor' in self;
/* Whether the browser is WebKit-based with internal test-only API enabled */
export const isWebKitBased = !isChromiumBased && 'internals' in self;
Content-Type: text/javascript; charset=utf-8
Cache-Control: max-age=3600
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment