chromium/third_party/blink/web_tests/wpt_internal/digital-goods/digital-goods-interface.https.html

<!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>