<!DOCTYPE html>
<html>
<head>
<title>Digital Goods API: Test calls from WebIDL to mojo and back.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script type="module">
import {digital_goods_test} from './resources/mock-digital-goods-service.js';
const { HTTPS_NOTSAMESITE_ORIGIN, REMOTE_ORIGIN } = get_host_info();
const SAME_SITE_HELPER_PATH = '/wpt_internal/digital-goods/resources/iframe-helper.html';
digital_goods_test(async service => {
assert_not_equals(service, undefined);
}, {title: 'Digital goods service exists.'});
digital_goods_test(async service => {
assert_not_equals(service, null);
}, {title: 'Digital goods service is available.'});
promise_test(async () => {
try {
await window.getDigitalGoodsService('');
assert_unreached();
} catch (error) {
assert_true(error instanceof TypeError);
assert_equals(error.name, 'TypeError');
assert_equals(error.message, 'Empty payment method');
}
}, 'Digital goods service rejects for empty payment method.');
promise_test(async () => {
try {
await window.getDigitalGoodsService('https://not.known/payment/method');
assert_unreached();
} catch (error) {
assert_true(error instanceof DOMException);
assert_equals(error.name, 'OperationError');
assert_equals(error.message, 'unsupported payment method');
}
}, 'Digital goods service rejects for unknown payment method.');
promise_test(() => {
let iframe = document.createElement('iframe');
iframe.src = HTTPS_NOTSAMESITE_ORIGIN + SAME_SITE_HELPER_PATH;
iframe.allow = 'payment';
iframe.onload = () => {
iframe.contentWindow.postMessage('Ready', '*');
}
document.body.appendChild(iframe);
return new Promise((resolve, reject) => {
window.onmessage = message => {
if (message.data == 'Failure: NotAllowedError: Access denied from cross-site frames') {
resolve();
} else {
reject(message.data)
}
}
});
}, 'Digital goods service rejects from within cross-site iframe.');
promise_test(() => {
let iframe = document.createElement('iframe');
// REMOTE_ORIGIN is a subdomain of the main origin.
iframe.src = REMOTE_ORIGIN + SAME_SITE_HELPER_PATH;
iframe.allow = 'payment';
iframe.onload = () => {
iframe.contentWindow.postMessage('Ready', '*');
}
document.body.appendChild(iframe);
return new Promise((resolve, reject) => {
window.onmessage = message => {
if (message.data == 'Success') {
resolve();
} else {
reject(message.data)
}
}
});
}, 'Digital goods service passes same-site check from within subdomain iframe.');
digital_goods_test(async service => {
const response = await service.getDetails(['id2']);
assert_equals(response.length, 1);
assert_equals(response[0].itemId, 'id2');
assert_equals(response[0].title, 'title:id2');
assert_equals(response[0].description, 'description:id2');
assert_equals(response[0].price.currency, 'AUD');
assert_equals(response[0].price.value, '2.00');
// Optional fields are absent or empty.
assert_equals(response[0].type, undefined);
assert_array_equals(response[0].iconURLs, []);
assert_equals(response[0].subscriptionPeriod, undefined);
assert_equals(response[0].freeTrialPeriod, undefined);
assert_equals(response[0].introductoryPrice, undefined);
assert_equals(response[0].introductoryPricePeriod, undefined);
assert_equals(response[0].introductoryPriceCycles, undefined);
}, {title: 'GetDetails round trip, required fields.'});
digital_goods_test(async service => {
const response = await service.getDetails(['id1', 'id3']);
assert_equals(response.length, 2);
assert_equals(response[0].itemId, 'id1');
assert_equals(response[0].title, 'title:id1');
assert_equals(response[0].price.currency, 'AUD');
assert_equals(response[0].price.value, '1.00');
assert_equals(response[0].type, 'subscription');
assert_equals(response[0].description, 'description:id1');
assert_equals(response[0].iconURLs.length, 1, 'iconURLs.length');
assert_equals(response[0].iconURLs[0], 'https://example.com/icon.png');
assert_equals(response[0].subscriptionPeriod, 'P1Y');
assert_equals(response[0].freeTrialPeriod, 'P1M');
assert_equals(response[0].introductoryPrice.currency, 'JPY');
assert_equals(response[0].introductoryPrice.value, '2');
assert_equals(response[0].introductoryPricePeriod, 'P1D');
assert_equals(response[0].introductoryPriceCycles, 3);
// itemType has two valid values.
assert_equals(response[1].type, 'product');
}, {title: 'GetDetails round trip, optional fields.'});
digital_goods_test(async service => {
const response = await service.getDetails(['id1', 'id2', 'id3', 'id4']);
assert_equals(response.length, 4);
const responseIds = response.map(i => i.itemId);
assert_array_equals(responseIds, ['id1', 'id2', 'id3', 'id4']);
}, {title: 'GetDetails multiple IDs.'});
digital_goods_test(async service => {
try {
await service.getDetails([]);
assert_unreached();
} catch (error) {
assert_equals(error.message, 'Must specify at least one item ID.');
}
}, {title: 'GetDetails no IDs should error.'});
digital_goods_test(async service => {
const response = await service.getDetails(['gone']);
assert_equals(response.length, 0);
}, {title: 'GetDetails none found.'});
digital_goods_test(async service => {
const response = await service.getDetails(['id1', 'gone1', 'id2', 'gone2']);
assert_equals(response.length, 2);
}, {title: 'GetDetails not all found.'});
digital_goods_test(async service => {
try {
await service.getDetails(['fail']);
assert_unreached();
} catch (error) {
assert_true(error instanceof DOMException);
assert_equals(error.name, 'OperationError');
assert_equals(error.message, 'error');
}
}, {title: 'GetDetails internal error.'});
digital_goods_test(async service => {
const response = await service.consume('token1');
assert_equals(response, undefined);
}, {
expectedAction: 'consume:token1',
title: 'Consume a purchase token.'
});
digital_goods_test(async service => {
try {
await service.consume('fail');
assert_unreached();
} catch (error) {
assert_true(error instanceof DOMException);
assert_equals(error.name, 'OperationError');
assert_equals(error.message, 'error');
}
}, {title: 'Consume with bad purchase token should error.'});
digital_goods_test(async service => {
const response = await service.listPurchases();
assert_equals(response.length, 10);
assert_equals(response[0].itemId, 'id:0');
assert_equals(response[0].purchaseToken, 'purchaseToken:0');
// Should not have DGAPI v1 fields exposed.
assert_equals(response[0].acknowledged, undefined);
assert_equals(response[1].itemId, 'id:1');
assert_equals(response[1].purchaseToken, 'purchaseToken:1');
}, {title: 'ListPurchases round trip.'});
// TODO (crbug.com/1250604): Add ListPurchaseHistory test with blink impl.
digital_goods_test(async service => {
const response = await service.listPurchaseHistory();
assert_equals(response.length, 20);
assert_equals(response[0].itemId, 'id:0');
assert_equals(response[0].purchaseToken, 'purchaseToken:0');
// Should not have DGAPI v1 fields exposed.
assert_equals(response[0].acknowledged, undefined);
assert_equals(response[1].itemId, 'id:1');
assert_equals(response[1].purchaseToken, 'purchaseToken:1');
}, {title: 'ListPurchaseHistory round trip.'});
digital_goods_test(async service => {
const response1 = await service.getDetails(['id1']);
assert_equals(response1.length, 1);
gc();
const response2 = await service.getDetails(['id2']);
assert_equals(response2.length, 1);
gc();
const response3 = await service.getDetails(['id3']);
assert_equals(response3.length, 1);
}, {title: 'DigitalGoods referenced by scripts should survive garbage collector.'});
</script>
</body>
</html>