/* This file is part of GNU Taler (C) 2019 GNUnet e.V. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import test from "ava"; import { AmountString } from "./types-taler-common.js"; import { parseAddExchangeUri, parseDevExperimentUri, parsePayPullUri, parsePayPushUri, parsePayTemplateUri, parsePayUri, parseRefundUri, parseRestoreUri, parseWithdrawExchangeUri, parseWithdrawUri, stringifyAddExchange, stringifyDevExperimentUri, stringifyPayPullUri, stringifyPayPushUri, stringifyPayTemplateUri, stringifyPayUri, stringifyRefundUri, stringifyRestoreUri, stringifyWithdrawExchange, stringifyWithdrawUri, } from "./taleruri.js"; /** * 5.1 action: withdraw https://lsd.gnunet.org/lsd0006/#name-action-withdraw */ test("taler withdraw uri parsing", (t) => { const url1 = "taler://withdraw/bank.example.com/12345"; const r1 = parseWithdrawUri(url1); if (!r1) { t.fail(); return; } t.is(r1.withdrawalOperationId, "12345"); t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/"); }); test("taler withdraw uri parsing with external confirmation", (t) => { const url1 = "taler://withdraw/bank.example.com/12345?external-confirmation=1"; const r1 = parseWithdrawUri(url1); if (!r1) { t.fail(); return; } t.is(r1.externalConfirmation, true); t.is(r1.withdrawalOperationId, "12345"); t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/"); }); test("taler withdraw uri parsing (http)", (t) => { const url1 = "taler+http://withdraw/bank.example.com/12345"; const r1 = parseWithdrawUri(url1); if (!r1) { t.fail(); return; } t.is(r1.withdrawalOperationId, "12345"); t.is(r1.bankIntegrationApiBaseUrl, "http://bank.example.com/"); }); test("taler withdraw URI (stringify)", (t) => { const url = stringifyWithdrawUri({ bankIntegrationApiBaseUrl: "https://bank.taler.test/integration-api/", withdrawalOperationId: "123", }); t.deepEqual(url, "taler://withdraw/bank.taler.test/integration-api/123"); }); /** * 5.2 action: pay https://lsd.gnunet.org/lsd0006/#name-action-pay */ test("taler pay url parsing: defaults", (t) => { const url1 = "taler://pay/example.com/myorder/"; const r1 = parsePayUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "https://example.com/"); t.is(r1.sessionId, ""); const url2 = "taler://pay/example.com/myorder/mysession"; const r2 = parsePayUri(url2); if (!r2) { t.fail(); return; } t.is(r2.merchantBaseUrl, "https://example.com/"); t.is(r2.sessionId, "mysession"); }); test("taler pay url parsing: instance", (t) => { const url1 = "taler://pay/example.com/instances/myinst/myorder/"; const r1 = parsePayUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/"); t.is(r1.orderId, "myorder"); }); test("taler pay url parsing (claim token)", (t) => { const url1 = "taler://pay/example.com/instances/myinst/myorder/?c=ASDF"; const r1 = parsePayUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/"); t.is(r1.orderId, "myorder"); t.is(r1.claimToken, "ASDF"); }); test("taler pay uri parsing: non-https", (t) => { const url1 = "taler+http://pay/example.com/myorder/"; const r1 = parsePayUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "http://example.com/"); t.is(r1.orderId, "myorder"); }); test("taler pay uri parsing: missing session component", (t) => { const url1 = "taler+http://pay/example.com/myorder"; const r1 = parsePayUri(url1); if (r1) { t.fail(); return; } t.pass(); }); test("taler pay URI (stringify)", (t) => { const url1 = stringifyPayUri({ merchantBaseUrl: "http://localhost:123/", orderId: "foo", sessionId: "", }); t.deepEqual(url1, "taler+http://pay/localhost:123/foo/"); const url2 = stringifyPayUri({ merchantBaseUrl: "http://localhost:123/", orderId: "foo", sessionId: "bla", }); t.deepEqual(url2, "taler+http://pay/localhost:123/foo/bla"); }); test("taler pay URI (stringify with https)", (t) => { const url1 = stringifyPayUri({ merchantBaseUrl: "https://localhost:123/", orderId: "foo", sessionId: "", }); t.deepEqual(url1, "taler://pay/localhost:123/foo/"); const url2 = stringifyPayUri({ merchantBaseUrl: "https://localhost/", orderId: "foo", sessionId: "bla", noncePriv: "123", }); t.deepEqual(url2, "taler://pay/localhost/foo/bla?n=123"); }); /** * 5.3 action: refund https://lsd.gnunet.org/lsd0006/#name-action-refund */ test("taler refund uri parsing: non-https #1", (t) => { const url1 = "taler+http://refund/example.com/myorder/"; const r1 = parseRefundUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "http://example.com/"); t.is(r1.orderId, "myorder"); }); test("taler refund uri parsing", (t) => { const url1 = "taler://refund/merchant.example.com/1234/"; const r1 = parseRefundUri(url1); if (!r1) { t.fail(); return; } t.is(r1.merchantBaseUrl, "https://merchant.example.com/"); t.is(r1.orderId, "1234"); }); test("taler refund uri parsing with instance", (t) => { const url1 = "taler://refund/merchant.example.com/instances/myinst/1234/"; const r1 = parseRefundUri(url1); if (!r1) { t.fail(); return; } t.is(r1.orderId, "1234"); t.is(r1.merchantBaseUrl, "https://merchant.example.com/instances/myinst/"); }); test("taler refund URI (stringify)", (t) => { const url = stringifyRefundUri({ merchantBaseUrl: "https://merchant.test/instance/pepe/", orderId: "123", }); t.deepEqual(url, "taler://refund/merchant.test/instance/pepe/123/"); }); /** * 5.5 action: pay-push https://lsd.gnunet.org/lsd0006/#name-action-pay-push */ test("taler peer to peer push URI", (t) => { const url1 = "taler://pay-push/exch.example.com/foo"; const r1 = parsePayPushUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "https://exch.example.com/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer push URI (path)", (t) => { const url1 = "taler://pay-push/exch.example.com:123/bla/foo"; const r1 = parsePayPushUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "https://exch.example.com:123/bla/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer push URI (http)", (t) => { const url1 = "taler+http://pay-push/exch.example.com:123/bla/foo"; const r1 = parsePayPushUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "http://exch.example.com:123/bla/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer push URI (stringify)", (t) => { const url = stringifyPayPushUri({ exchangeBaseUrl: "https://foo.example.com/bla/", contractPriv: "123", }); t.deepEqual(url, "taler://pay-push/foo.example.com/bla/123"); }); /** * 5.6 action: pay-pull https://lsd.gnunet.org/lsd0006/#name-action-pay-pull */ test("taler peer to peer pull URI", (t) => { const url1 = "taler://pay-pull/exch.example.com/foo"; const r1 = parsePayPullUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "https://exch.example.com/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer pull URI (path)", (t) => { const url1 = "taler://pay-pull/exch.example.com:123/bla/foo"; const r1 = parsePayPullUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "https://exch.example.com:123/bla/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer pull URI (http)", (t) => { const url1 = "taler+http://pay-pull/exch.example.com:123/bla/foo"; const r1 = parsePayPullUri(url1); if (!r1) { t.fail(); return; } t.is(r1.exchangeBaseUrl, "http://exch.example.com:123/bla/"); t.is(r1.contractPriv, "foo"); }); test("taler peer to peer pull URI (stringify)", (t) => { const url = stringifyPayPullUri({ exchangeBaseUrl: "https://foo.example.com/bla/", contractPriv: "123", }); t.deepEqual(url, "taler://pay-pull/foo.example.com/bla/123"); }); /** * 5.7 action: pay-template https://lsd.gnunet.org/lsd0006/#name-action-pay-template */ test("taler pay template URI (parsing)", (t) => { const url1 = "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; const r1 = parsePayTemplateUri(url1); if (!r1) { t.fail(); return; } t.deepEqual(r1.merchantBaseUrl, "https://merchant.example.com/"); t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"); }); test("taler pay template URI (parsing, http with port)", (t) => { const url1 = "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"; const r1 = parsePayTemplateUri(url1); if (!r1) { t.fail(); return; } t.deepEqual(r1.merchantBaseUrl, "http://merchant.example.com:1234/"); t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY"); }); test("taler pay template URI (stringify)", (t) => { const url1 = stringifyPayTemplateUri({ merchantBaseUrl: "http://merchant.example.com:1234/", templateId: "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", }); t.deepEqual( url1, "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY", ); }); /** * 5.10 action: restore https://lsd.gnunet.org/lsd0006/#name-action-restore */ test("taler restore URI (parsing, http with port)", (t) => { const r1 = parseRestoreUri( "taler+http://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:123", ); if (!r1) { t.fail(); return; } t.deepEqual( r1.walletRootPriv, "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", ); t.deepEqual(r1.providers[0], "http://prov1.example.com/"); t.deepEqual(r1.providers[1], "http://prov2.example.com:123/"); }); test("taler restore URI (parsing, https with port)", (t) => { const r1 = parseRestoreUri( "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:234,https%3A%2F%2Fprov1.example.com%2F", ); if (!r1) { t.fail(); return; } t.deepEqual( r1.walletRootPriv, "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", ); t.deepEqual(r1.providers[0], "https://prov1.example.com/"); t.deepEqual(r1.providers[1], "https://prov2.example.com:234/"); }); test("taler restore URI (stringify)", (t) => { const url = stringifyRestoreUri({ walletRootPriv: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", providers: ["http://prov1.example.com", "https://prov2.example.com:234/"], }); t.deepEqual( url, "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/http%3A%2F%2Fprov1.example.com%2F,https%3A%2F%2Fprov2.example.com%3A234%2F", ); }); /** * 5.11 action: dev-experiment https://lsd.gnunet.org/lsd0006/#name-action-dev-experiment */ test("taler dev exp URI (parsing)", (t) => { const url1 = "taler://dev-experiment/123"; const r1 = parseDevExperimentUri(url1); if (!r1) { t.fail(); return; } t.deepEqual(r1.devExperimentId, "123"); }); test("taler dev exp URI (stringify)", (t) => { const url1 = stringifyDevExperimentUri({ devExperimentId: "123", }); t.deepEqual(url1, "taler://dev-experiment/123"); }); /** * 5.12 action: withdraw-exchange https://lsd.gnunet.org/lsd0006/#name-action-withdraw-exchange */ test("taler withdraw exchange URI (parse)", (t) => { // Pubkey has been phased out, may no longer be specified. { const rx1 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/someroot/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A2", ); if (rx1) { t.fail(); return; } } { const rx2 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0", ); if (rx2) { t.fail(); return; } } // Now test well-formed URIs { const r2 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/someroot/", ); if (!r2) { t.fail(); return; } t.deepEqual(r2.amount, undefined); t.deepEqual( r2.exchangeBaseUrl, "https://exchange.demo.taler.net/someroot/", ); } { const r3 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net/", ); if (!r3) { t.fail(); return; } t.deepEqual(r3.amount, undefined); t.deepEqual(r3.exchangeBaseUrl, "https://exchange.demo.taler.net/"); } { // No trailing slash, no path component const r4 = parseWithdrawExchangeUri( "taler://withdraw-exchange/exchange.demo.taler.net", ); if (!r4) { t.fail(); return; } t.deepEqual(r4.amount, undefined); t.deepEqual(r4.exchangeBaseUrl, "https://exchange.demo.taler.net/"); } }); test("taler withdraw exchange URI (stringify)", (t) => { const url = stringifyWithdrawExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", }); t.deepEqual(url, "taler://withdraw-exchange/exchange.demo.taler.net/"); }); test("taler withdraw exchange URI with amount (stringify)", (t) => { const url = stringifyWithdrawExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", amount: "KUDOS:19" as AmountString, }); t.deepEqual( url, "taler://withdraw-exchange/exchange.demo.taler.net/?a=KUDOS%3A19", ); }); /** * 5.13 action: add-exchange https://lsd.gnunet.org/lsd0006/#name-action-add-exchange */ test("taler add exchange URI (parse)", (t) => { { const r1 = parseAddExchangeUri( "taler://add-exchange/exchange.example.com/", ); if (!r1) { t.fail(); return; } t.deepEqual(r1.exchangeBaseUrl, "https://exchange.example.com/"); } { const r2 = parseAddExchangeUri( "taler://add-exchange/exchanges.example.com/api/", ); if (!r2) { t.fail(); return; } t.deepEqual(r2.exchangeBaseUrl, "https://exchanges.example.com/api/"); } }); test("taler add exchange URI (stringify)", (t) => { const url = stringifyAddExchange({ exchangeBaseUrl: "https://exchange.demo.taler.net", }); t.deepEqual(url, "taler://add-exchange/exchange.demo.taler.net/"); }); /** * wrong uris */ test("taler pay url parsing: wrong scheme", (t) => { const url1 = "talerfoo://"; const r1 = parsePayUri(url1); t.is(r1, undefined); const url2 = "taler://refund/a/b/c/d/e/f"; const r2 = parsePayUri(url2); t.is(r2, undefined); });