aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content_scripts/notify.ts35
-rw-r--r--lib/wallet/cryptoLib.ts1
-rw-r--r--lib/wallet/emscriptif.ts2
-rw-r--r--lib/wallet/types.ts3
-rw-r--r--lib/wallet/wallet.ts22
-rw-r--r--lib/wallet/wxMessaging.ts18
-rw-r--r--manifest.json2
-rw-r--r--package.json7
-rw-r--r--pages/confirm-contract.html64
-rw-r--r--pages/confirm-contract.tsx30
-rw-r--r--pages/confirm-create-reserve.html64
-rw-r--r--pages/confirm-create-reserve.tsx130
-rw-r--r--selenium/README7
-rw-r--r--selenium/test.py228
-rw-r--r--test/run_tests.js36
-rw-r--r--test/tests/taler.ts15
-rw-r--r--tsconfig.json2
17 files changed, 568 insertions, 98 deletions
diff --git a/content_scripts/notify.ts b/content_scripts/notify.ts
index 06ea2e92e..8e70b44c4 100644
--- a/content_scripts/notify.ts
+++ b/content_scripts/notify.ts
@@ -33,10 +33,6 @@ namespace TalerNotify {
console.log("Taler injected", chrome.runtime.id);
- // FIXME: only do this for test wallets?
- // This is no security risk, since the extension ID for published
- // extension is publicly known.
-
function subst(url: string, H_contract) {
url = url.replace("${H_contract}", H_contract);
url = url.replace("${$}", "$");
@@ -45,17 +41,28 @@ namespace TalerNotify {
const handlers = [];
- // Hack to know when the extension is unloaded
- let port = chrome.runtime.connect();
-
- port.onDisconnect.addListener(() => {
- console.log("chrome runtime disconnected, removing handlers");
- for (let handler of handlers) {
- document.removeEventListener(handler.type, handler.listener);
- }
- });
+ function init() {
+ chrome.runtime.sendMessage({type: "ping"}, () => {
+ if (chrome.runtime.lastError) {
+ console.log("extension not yet ready");
+ window.setTimeout(init, 200);
+ return;
+ }
+ console.log("got pong");
+ registerHandlers();
+ // Hack to know when the extension is unloaded
+ let port = chrome.runtime.connect();
+
+ port.onDisconnect.addListener(() => {
+ console.log("chrome runtime disconnected, removing handlers");
+ for (let handler of handlers) {
+ document.removeEventListener(handler.type, handler.listener);
+ }
+ });
+ });
+ }
- registerHandlers();
+ init();
function registerHandlers() {
const $ = (x) => document.getElementById(x);
diff --git a/lib/wallet/cryptoLib.ts b/lib/wallet/cryptoLib.ts
index 769cb3356..967fc23c8 100644
--- a/lib/wallet/cryptoLib.ts
+++ b/lib/wallet/cryptoLib.ts
@@ -125,6 +125,7 @@ namespace RpcFunctions {
fee_deposit: (new native.Amount(denom.fee_deposit)).toNbo(),
fee_refresh: (new native.Amount(denom.fee_refresh)).toNbo(),
fee_withdraw: (new native.Amount(denom.fee_withdraw)).toNbo(),
+ fee_refund: (new native.Amount(denom.fee_refund)).toNbo(),
});
let nativeSig = new native.EddsaSignature();
diff --git a/lib/wallet/emscriptif.ts b/lib/wallet/emscriptif.ts
index b03bc9bc7..4ec029e52 100644
--- a/lib/wallet/emscriptif.ts
+++ b/lib/wallet/emscriptif.ts
@@ -866,6 +866,7 @@ export interface DenominationKeyValidityPS_args {
fee_withdraw: AmountNbo;
fee_deposit: AmountNbo;
fee_refresh: AmountNbo;
+ fee_refund: AmountNbo;
denom_hash: HashCode;
}
@@ -889,6 +890,7 @@ export class DenominationKeyValidityPS extends SignatureStruct {
["fee_withdraw", AmountNbo],
["fee_deposit", AmountNbo],
["fee_refresh", AmountNbo],
+ ["fee_refund", AmountNbo],
["denom_hash", HashCode]
];
}
diff --git a/lib/wallet/types.ts b/lib/wallet/types.ts
index 8aa03d82b..5045a5b9a 100644
--- a/lib/wallet/types.ts
+++ b/lib/wallet/types.ts
@@ -75,6 +75,9 @@ export class Denomination {
@Checkable.Value(AmountJson)
fee_refresh: AmountJson;
+ @Checkable.Value(AmountJson)
+ fee_refund: AmountJson;
+
@Checkable.String
stamp_start: string;
diff --git a/lib/wallet/wallet.ts b/lib/wallet/wallet.ts
index dde7a8248..3c9f3ffed 100644
--- a/lib/wallet/wallet.ts
+++ b/lib/wallet/wallet.ts
@@ -522,6 +522,28 @@ export class Wallet {
/**
+ * Add a contract to the wallet and sign coins,
+ * but do not send them yet.
+ */
+ checkPay(offer: Offer): Promise<any> {
+ console.log("executing checkPay");
+ return Promise.resolve().then(() => {
+ return this.getPossibleExchangeCoins(offer.contract.amount,
+ offer.contract.max_fee,
+ offer.contract.exchanges)
+ }).then((mcs) => {
+ if (Object.keys(mcs).length == 0) {
+ console.log("not confirming payment, insufficient coins");
+ return {
+ error: "coins-insufficient",
+ };
+ }
+ return {};
+ });
+ }
+
+
+ /**
* Retrieve all necessary information for looking up the contract
* with the given hash.
*/
diff --git a/lib/wallet/wxMessaging.ts b/lib/wallet/wxMessaging.ts
index 64b16de8d..164342f4e 100644
--- a/lib/wallet/wxMessaging.ts
+++ b/lib/wallet/wxMessaging.ts
@@ -99,6 +99,24 @@ function makeHandlers(db: IDBDatabase,
return wallet.confirmPay(offer);
},
+ ["check-pay"]: function(detail, sender) {
+ let offer;
+ try {
+ offer = Offer.checked(detail.offer);
+ } catch (e) {
+ if (e instanceof Checkable.SchemaError) {
+ console.error("schema error:", e.message);
+ return Promise.resolve({
+ error: "invalid contract",
+ hint: e.message,
+ detail: detail
+ });
+ } else {
+ throw e;
+ }
+ }
+ return wallet.checkPay(offer);
+ },
["execute-payment"]: function(detail, sender) {
return wallet.executePayment(detail.H_contract);
},
diff --git a/manifest.json b/manifest.json
index 185232f57..5ede435e9 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"description": "Privacy preserving and transparent payments",
"manifest_version": 2,
"name": "GNU Taler Wallet (git)",
- "version": "0.5.19",
+ "version": "0.5.23",
"applications": {
"gecko": {
diff --git a/package.json b/package.json
index e36b22776..f86943a13 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,8 @@
"description": "",
"main": "wxwallet.js",
"scripts": {
- "test": "mocha --delay"
+ "test": "mocha --delay",
+ "test-cover": "istanbul cover -x libwrapper.js _mocha -- --delay"
},
"repository": {
"type": "git",
@@ -26,13 +27,15 @@
"gulp-tar": "^1.8.0",
"gulp-typescript": "^2.10.0",
"gulp-zip": "^3.1.0",
+ "istanbul-lib-instrument": "^1.0.0-alpha.6",
"jed": "^1.1.0",
"map-stream": "0.0.6",
- "mocha": "^2.3.4",
+ "mocha": "^2.4.5",
"po2json": "git+https://github.com/mikeedwards/po2json",
"systemjs": "^0.19.14",
"through2": "^2.0.1",
"typescript": "^1.9.0-dev.20160225",
+ "typhonjs-istanbul-instrument-jspm": "^0.1.0",
"vinyl": "^1.1.1"
}
}
diff --git a/pages/confirm-contract.html b/pages/confirm-contract.html
index 6abb65c67..ec7eab8c1 100644
--- a/pages/confirm-contract.html
+++ b/pages/confirm-contract.html
@@ -5,7 +5,6 @@
<title>Taler Wallet: Confirm Reserve Creation</title>
<link rel="stylesheet" type="text/css" href="../style/lang.css">
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
<script src="../lib/vendor/URI.js"></script>
<script src="../lib/vendor/mithril.js"></script>
@@ -15,18 +14,65 @@
<script src="../i18n/strings.js"></script>
<script src="../lib/i18n.js"></script>
<script src="../lib/module-trampoline.js"></script>
-</head>
-<body>
- <header>
- <div id="logo"></div>
- <h1>Payment Confirmation</h1>
- </header>
+ <style>
+ #main {
+ border: solid 1px black;
+ border-radius: 10px;
+ margin: auto;
+ max-width: 50%;
+ padding: 2em;
+ }
+
+ button.accept {
+ background-color: #5757D2;
+ border: 1px solid black;
+ border-radius: 5px;
+ margin: 1em 0;
+ padding: 0.5em;
+ font-weight: bold;
+ color: white;
+ }
+ button.linky {
+ background:none!important;
+ border:none;
+ padding:0!important;
+
+ font-family:arial,sans-serif;
+ color:#069;
+ text-decoration:underline;
+ cursor:pointer;
+ }
+
+ input.url {
+ width: 25em;
+ }
- <aside class="sidebar" id="left">
- </aside>
+ button.accept:disabled {
+ background-color: #dedbe8;
+ border: 1px solid white;
+ border-radius: 5px;
+ margin: 1em 0;
+ padding: 0.5em;
+ font-weight: bold;
+ color: #2C2C2C;
+ }
+
+ .errorbox {
+ border: 1px solid;
+ display: inline-block;
+ margin: 1em;
+ padding: 1em;
+ font-weight: bold;
+ background: #FF8A8A;
+ }
+ </style>
+</head>
+
+<body>
<section id="main">
+ <h1>GNU Taler Wallet</h1>
<article id="contract" class="fade"></article>
</section>
</body>
diff --git a/pages/confirm-contract.tsx b/pages/confirm-contract.tsx
index 0c7419c6c..2e055d4f1 100644
--- a/pages/confirm-contract.tsx
+++ b/pages/confirm-contract.tsx
@@ -72,6 +72,7 @@ export function main() {
console.dir(offer);
let contract = offer.contract;
let error = null;
+ let payDisabled = true;
var Contract = {
view(ctrl) {
@@ -87,8 +88,8 @@ export function main() {
_.map(contract.products,
(p: any) => m("li",
`${p.description}: ${prettyAmount(p.price)}`))),
- m("button.confirm-pay", {onclick: doPayment}, i18n`Confirm Payment`),
- m("p", error ? error : []),
+ m("button.accept", {onclick: doPayment, disabled: payDisabled}, i18n`Confirm Payment`),
+ (error ? m("p.errorbox", error) : []),
m(Details, contract)
];
}
@@ -96,6 +97,31 @@ export function main() {
m.mount(document.getElementById("contract"), Contract);
+ function checkPayment() {
+ chrome.runtime.sendMessage({type: 'check-pay', detail: {offer}}, (resp) => {
+ if (resp.error) {
+ console.log("check-pay error", JSON.stringify(resp));
+ switch (resp.error) {
+ case "coins-insufficient":
+ error = "You do not have enough coins of the requested currency.";
+ break;
+ default:
+ error = `Error: ${resp.error}`;
+ break;
+ }
+ payDisabled = true;
+ } else {
+ payDisabled = false;
+ error = null;
+ }
+ m.redraw();
+ window.setTimeout(checkPayment, 300);
+ });
+ }
+
+ checkPayment();
+
+
function doPayment() {
let d = {offer};
chrome.runtime.sendMessage({type: 'confirm-pay', detail: d}, (resp) => {
diff --git a/pages/confirm-create-reserve.html b/pages/confirm-create-reserve.html
index ab0a7c954..1612340e8 100644
--- a/pages/confirm-create-reserve.html
+++ b/pages/confirm-create-reserve.html
@@ -4,8 +4,6 @@
<head>
<title>Taler Wallet: Select Taler Provider</title>
- <link rel="stylesheet" type="text/css" href="../style/wallet.css">
-
<script src="../lib/vendor/URI.js"></script>
<script src="../lib/vendor/mithril.js"></script>
<script src="../lib/vendor/system-csp-production.src.js"></script>
@@ -13,21 +11,63 @@
<script src="../i18n/strings.js"></script>
<script src="../lib/i18n.js"></script>
<script src="../lib/module-trampoline.js"></script>
-</head>
-<body>
- <header>
- <div id="logo"></div>
- <h1>Select Taler Provider</h1>
- </header>
+ <style>
+ #main {
+ border: solid 1px black;
+ border-radius: 10px;
+ margin: auto;
+ max-width: 50%;
+ padding: 2em;
+ }
+
+ button.accept {
+ background-color: #5757D2;
+ border: 1px solid black;
+ border-radius: 5px;
+ margin: 1em 0;
+ padding: 0.5em;
+ font-weight: bold;
+ color: white;
+ }
+ button.linky {
+ background:none!important;
+ border:none;
+ padding:0!important;
+
+ font-family:arial,sans-serif;
+ color:#069;
+ text-decoration:underline;
+ cursor:pointer;
+ }
+
+ input.url {
+ width: 25em;
+ }
- <aside class="sidebar" id="left">
- </aside>
+ table {
+ border-collapse: collapse;
+ }
+ td {
+ border-left: 1px solid black;
+ border-right: 1px solid black;
+ text-align: center;
+ padding: 0.3em;
+ }
+
+ span.spacer {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ }
+
+ </style>
+</head>
+
+<body>
<section id="main">
- <article>
+ <h1>GNU Taler Wallet</h1>
<div class="fade" id="exchange-selection"></div>
- </article>
</section>
</body>
diff --git a/pages/confirm-create-reserve.tsx b/pages/confirm-create-reserve.tsx
index be84fff2b..f490451ca 100644
--- a/pages/confirm-create-reserve.tsx
+++ b/pages/confirm-create-reserve.tsx
@@ -76,31 +76,38 @@ class Controller {
callbackUrl: string;
wtTypes: string[];
detailCollapsed = m.prop<boolean>(true);
+ suggestedExchangeUrl: string;
+ complexViewRequested = false;
+ urlOkay = false;
- constructor(initialExchangeUrl: string,
+ constructor(suggestedExchangeUrl: string,
amount: AmountJson,
callbackUrl: string,
wt_types: string[]) {
console.log("creating main controller");
+ this.suggestedExchangeUrl = suggestedExchangeUrl;
this.amount = amount;
this.callbackUrl = callbackUrl;
this.wtTypes = wt_types;
this.timer = new DelayTimer(800, () => this.update());
- this.url(initialExchangeUrl);
+ this.url(suggestedExchangeUrl);
this.update();
}
private update() {
this.timer.stop();
const doUpdate = () => {
+ this.reserveCreationInfo = null;
if (!this.url()) {
- this.statusString = i18n`Please enter a URL`;
+ this.statusString = i18n`Error: URL is empty`;
+ m.redraw(true);
return;
}
this.statusString = null;
let parsedUrl = URI(this.url());
if (parsedUrl.is("relative")) {
- this.statusString = i18n`The URL you've entered is not valid (must be absolute)`;
+ this.statusString = i18n`Error: URL may not be relative`;
+ m.redraw(true);
return;
}
@@ -114,15 +121,15 @@ class Controller {
this.isValidExchange = true;
this.reserveCreationInfo = r;
console.dir(r);
- this.statusString = "The exchange base URL is valid!";
m.endComputation();
})
.catch((e) => {
console.log("get exchange info rejected");
if (e.hasOwnProperty("httpStatus")) {
- this.statusString = `request failed with status ${this.request.status}`;
- } else {
- this.statusString = `unknown request error`;
+ this.statusString = `Error: request failed with status ${this.request.status}`;
+ } else if (e.hasOwnProperty("errorResponse")) {
+ let resp = e.errorResponse;
+ this.statusString = `Error: ${resp.error} (${resp.hint})`;
}
m.endComputation();
});
@@ -130,7 +137,7 @@ class Controller {
doUpdate();
- console.log("got update");
+ console.log("got update", this.url());
}
reset() {
@@ -187,14 +194,82 @@ class Controller {
}
}
-
-function view(ctrl: Controller) {
+function view(ctrl: Controller): any {
let controls = [];
let mx = (x, ...args) => controls.push(m(x, ...args));
mx("p",
- i18n`The bank wants to create a reserve over ${amountToPretty(
- ctrl.amount)}.`);
+ i18n.parts`You are about to withdraw ${m("strong", amountToPretty(
+ ctrl.amount))} from your bank account into your wallet.`);
+
+ if (ctrl.complexViewRequested || !ctrl.suggestedExchangeUrl) {
+ return controls.concat(viewComplex(ctrl));
+ }
+
+ return controls.concat(viewSimple(ctrl));
+}
+
+function viewSimple(ctrl: Controller) {
+ let controls = [];
+ let mx = (x, ...args) => controls.push(m(x, ...args));
+
+ if (ctrl.statusString) {
+ mx("p", "Error: ", ctrl.statusString);
+ mx("button.linky", {
+ onclick: () => {
+ ctrl.complexViewRequested = true;
+ }
+ }, "advanced options");
+ }
+ else if (ctrl.reserveCreationInfo) {
+ mx("button.accept", {
+ onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo,
+ ctrl.url(),
+ ctrl.amount,
+ ctrl.callbackUrl),
+ disabled: !ctrl.isValidExchange
+ },
+ "Accept fees and withdraw");
+ mx("span.spacer");
+ mx("button.linky", {
+ onclick: () => {
+ ctrl.complexViewRequested = true;
+ }
+ }, "advanced options");
+ let totalCost = Amounts.add(ctrl.reserveCreationInfo.overhead,
+ ctrl.reserveCreationInfo.withdrawFee).amount;
+ mx("p", `Withdraw cost: ${amountToPretty(totalCost)}`);
+ } else {
+ mx("p", "Please wait ...");
+ }
+
+
+ return controls;
+}
+
+
+function viewComplex(ctrl: Controller) {
+ let controls = [];
+ let mx = (x, ...args) => controls.push(m(x, ...args));
+
+ mx("button.accept", {
+ onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo,
+ ctrl.url(),
+ ctrl.amount,
+ ctrl.callbackUrl),
+ disabled: !ctrl.isValidExchange
+ },
+ "Accept fees and withdraw");
+ mx("span.spacer");
+ mx("button.linky", {
+ onclick: () => {
+ ctrl.complexViewRequested = false;
+ }
+ }, "back to simple view");
+
+ mx("br");
+
+
mx("input",
{
className: "url",
@@ -204,18 +279,11 @@ function view(ctrl: Controller) {
oninput: m.withAttr("value", ctrl.onUrlChanged.bind(ctrl)),
});
- mx("button", {
- onclick: () => ctrl.confirmReserve(ctrl.reserveCreationInfo,
- ctrl.url(),
- ctrl.amount,
- ctrl.callbackUrl),
- disabled: !ctrl.isValidExchange
- },
- "Confirm exchange selection");
+ mx("br");
if (ctrl.statusString) {
mx("p", ctrl.statusString);
- } else {
+ } else if (!ctrl.reserveCreationInfo) {
mx("p", "Checking URL, please wait ...");
}
@@ -246,9 +314,21 @@ function view(ctrl: Controller) {
function renderReserveCreationDetails(rci: ReserveCreationInfo) {
let denoms = rci.selectedDenoms;
+ let countByPub = {};
+ let uniq = [];
+
+ denoms.forEach((x: Denomination) => {
+ let c = countByPub[x.denom_pub] || 0;
+ if (c == 0) {
+ uniq.push(x);
+ }
+ c += 1;
+ countByPub[x.denom_pub] = c;
+ });
+
function row(denom: Denomination) {
return m("tr", [
- m("td", denom.pub_hash.substr(0, 5) + "..."),
+ m("td", countByPub[denom.denom_pub] + "x"),
m("td", amountToPretty(denom.value)),
m("td", amountToPretty(denom.fee_withdraw)),
m("td", amountToPretty(denom.fee_refresh)),
@@ -263,13 +343,13 @@ function renderReserveCreationDetails(rci: ReserveCreationInfo) {
m("p", `Overhead: ${overheadStr}`),
m("table", [
m("tr", [
- m("th", "Key Hash"),
+ m("th", "Count"),
m("th", "Value"),
m("th", "Withdraw Fee"),
m("th", "Refresh Fee"),
m("th", "Deposit Fee"),
]),
- denoms.map(row)
+ uniq.map(row)
])
];
}
diff --git a/selenium/README b/selenium/README
new file mode 100644
index 000000000..de99e6610
--- /dev/null
+++ b/selenium/README
@@ -0,0 +1,7 @@
+Directory containing testscases for testing the wallet with Selenium ChromeDriver.
+
+[1] Contains the ChromeDriver Pythonic documentation
+[2] Tells which fields (and which values) the 'loggingPrefs' capability expects
+
+[1] http://seleniumhq.github.io/selenium/docs/api/py/index.html
+[2] https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities#loggingpreferences-json-object
diff --git a/selenium/test.py b/selenium/test.py
new file mode 100644
index 000000000..14140e953
--- /dev/null
+++ b/selenium/test.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env python3
+
+"""
+Tests for the wallet. It looks for an env variable called TALER_BASEURL
+where it appends "/banks" etc. in order to find bank and shops. If not
+found, it defaults to https://test.taler.net/
+"""
+
+from selenium import webdriver
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from urllib import parse
+import argparse
+import time
+import logging
+import sys
+import os
+import re
+
+logger = logging.getLogger(__name__)
+taler_baseurl = os.environ.get('TALER_BASEURL', 'https://test.taler.net/')
+
+def client_setup(args):
+ """Return a dict containing the driver and the extension's id"""
+ co = webdriver.ChromeOptions()
+ co.add_argument("load-extension=" + args.extdir)
+ cap = webdriver.DesiredCapabilities.CHROME.copy()
+ cap['loggingPrefs'] = {'driver': 'INFO', 'browser': 'INFO'}
+ client = webdriver.Chrome(chrome_options=co, desired_capabilities=cap)
+ client.get('https://taler.net')
+ listener = """\
+ document.addEventListener('taler-id', function(evt){
+ var html = document.getElementsByTagName('html')[0];
+ html.setAttribute('data-taler-wallet-id', evt.detail.id);
+ });
+
+ var evt = new CustomEvent('taler-query-id');
+ document.dispatchEvent(evt);
+ """
+ client.execute_script(listener)
+ html = client.find_element(By.TAG_NAME, "html")
+ return {'client': client, 'ext_id': html.get_attribute('data-taler-wallet-id')}
+
+def is_error(client):
+ """Return True in case of errors in the browser, False otherwise"""
+ for log_type in ['browser']:
+ for log in client.get_log(log_type):
+ if log['level'] is 'error':
+ print(log['level'] + ': ' + log['message'])
+ return True
+ return False
+
+
+def switch_base():
+ """If 'test' is in TALER_BASEURL, then make it be 'demo', and viceversa.
+ Used to trig currency mismatch errors. It assumes that the https://{test,demo}.taler.net
+ layout is being used"""
+ global taler_baseurl
+ url = parse.urlparse(taler_baseurl)
+ if url[1] == 'test.taler.net':
+ taler_baseurl = "https://demo.taler.net"
+ if url[1] == 'demo.taler.net':
+ taler_baseurl = "https://test.taler.net"
+
+def make_donation(client, amount_value=None):
+ """Make donation at shop.test.taler.net. Assume the wallet has coins"""
+ client.get(parse.urljoin(taler_baseurl, "shop"))
+ try:
+ form = client.find_element(By.TAG_NAME, "form")
+ except NoSuchElementException:
+ logger.error('No donation form found')
+ sys.exit(1)
+ if amount_value:
+ xpath = "//select[@id='taler-donation']/option[@value='" + str(amount_value) + "']"
+ try:
+ desired_amount = client.find_element(By.XPATH, xpath)
+ desired_amount.click()
+ except NoSuchElementException:
+ logger.error("value '" + str(amount_value) + "' is not offered by this shop to donate, please adapt it")
+ sys.exit(1)
+ form.submit() # amount and receiver chosen
+ try:
+ confirm_taler = client.find_element(By.XPATH, "//form//input[@type='button']")
+ except NoSuchElementException:
+ logger.error('Could not trigger contract on donation shop')
+ sys.exit(1)
+ confirm_taler.click() # Taler as payment option chosen
+ # explicit get() is needed, it hangs (sometimes) otherwise
+ time.sleep(1)
+ client.get(client.current_url)
+ wait = WebDriverWait(client, 10)
+ try:
+ confirm_pay = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@class='accept']")))
+ except TimeoutException:
+ logger.error('Could not confirm payment on donation shop')
+ sys.exit(1)
+ confirm_pay.click()
+
+
+def buy_article(client):
+ """Buy article at blog.test.taler.net. Assume the wallet has coins"""
+ client.get(parse.urljoin(taler_baseurl, "blog"))
+ try:
+ teaser = client.find_element(By.XPATH, "//ul/h3/a[1]") # Pick 'Foreword' chapter
+ except NoSuchElementException:
+ logger.error('Could not choose "Foreword" chapter on blog')
+ sys.exit(1)
+ teaser.click()
+ # explicit get() is needed, it hangs (sometimes) otherwise
+ time.sleep(1)
+ client.get(client.current_url)
+ wait = WebDriverWait(client, 10)
+ try:
+ confirm_pay = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[@class='accept']")))
+ except TimeoutException:
+ logger.error('Could not confirm payment on blog')
+ sys.exit(1)
+ confirm_pay.click()
+
+
+def register(client):
+ """Register a new user to the bank delaying its execution until the
+ profile page is shown"""
+ client.get(parse.urljoin(taler_baseurl, "bank"))
+ try:
+ register_link = client.find_element(By.XPATH, "//a[@href='/accounts/register/']")
+ except NoSuchElementException:
+ logger.error("Could not find register link on bank's homepage")
+ sys.exit(1)
+ register_link.click()
+ try:
+ client.find_element(By.TAG_NAME, "form")
+ except NoSuchElementException:
+ logger.error("Register form not found")
+ sys.exit(1)
+
+ register = """\
+ var form = document.getElementsByTagName('form')[0];
+ form.username.value = '%s';
+ form.password.value = 'test';
+ form.submit();
+ """ % str(int(time.time())) # need fresh username
+
+ client.execute_script(register)
+ # need implicit wait to be set up
+ try:
+ button = client.find_element(By.ID, "select-exchange")
+ except NoSuchElementException:
+ logger.error("Selecting exchange impossible")
+ sys.exit(1)
+ # when button is gotten, the browser is in the profile page
+ # so the function can return
+ if not is_error(client):
+ logger.info('correctly registered at bank')
+ else:
+ logger.error('User not registered at bank')
+
+
+def withdraw(client, amount_value=None):
+ """Register and withdraw (1) KUDOS for a fresh user"""
+ register(client)
+ # trigger withdrawal button
+ try:
+ button = client.find_element(By.ID, "select-exchange")
+ except NoSuchElementException:
+ logger.error("Selecting exchange impossible")
+ sys.exit(1)
+ if amount_value:
+ xpath = "//select/option[@value='" + str(amount_value) + "']"
+ try:
+ desired_amount = client.find_element(By.XPATH, xpath)
+ desired_amount.click()
+ except NoSuchElementException:
+ logger.error("value '" + str(amount_value) + "' is not offered by this bank to withdraw, please adapt it")
+ sys.exit(1)
+ button.click()
+ location = client.execute_script("return document.location.href")
+ client.get(location)
+ # Confirm xchg
+ wait = WebDriverWait(client, 10)
+ try:
+ button = wait.until(EC.element_to_be_clickable((By.XPATH, "//button[1]")))
+ except TimeoutException:
+ logger.error("Could not confirm exchange (therefore provide withdrawal needed data)")
+ sys.exit(1)
+ # This click returns the captcha page (put wait?)
+ button.click()
+ try:
+ answer = client.find_element(By.XPATH, "//input[@name='pin_0']")
+ question = client.find_element(By.XPATH, "//span[@class='captcha-question']/div")
+ except NoSuchElementException:
+ logger.error("Captcha page not gotten or malformed")
+ sys.exit(1)
+ questionTok = question.text.split()
+ op1 = int(questionTok[2])
+ op2 = int(questionTok[4])
+ res = {'+': op1 + op2, '-': op1 - op2, u'\u00d7': op1 * op2}
+ answer.send_keys(res[questionTok[3]])
+ try:
+ form = client.find_element(By.TAG_NAME, "form")
+ except NoSuchElementException:
+ logger.error("Could not submit captcha answer (therefore trigger withdrawal)")
+ sys.exit(1)
+ form.submit()
+ # check outcome
+ try:
+ client.find_element(By.CLASS_NAME, "informational-ok")
+ except NoSuchElementException:
+ logger.error("Withdrawal not completed")
+ sys.exit(1)
+ logger.info("Withdrawal completed")
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('--extdir', help="Folder containing the unpacked extension", metavar="EXTDIR", type=str, dest="extdir", required=True)
+args = parser.parse_args()
+ret = client_setup(args)
+client = ret['client']
+client.implicitly_wait(10)
+withdraw(client, 10)
+switch_base() # inducing error
+make_donation(client, 6.0)
+buy_article(client)
+logger.info("Test passed")
+client.close()
+sys.exit(0)
diff --git a/test/run_tests.js b/test/run_tests.js
index f62810f7e..88aded741 100644
--- a/test/run_tests.js
+++ b/test/run_tests.js
@@ -12,44 +12,42 @@
let assert = require("better-assert");
let vm = require("vm");
let fs = require("fs");
-
+let instrument = require("typhonjs-istanbul-instrument-jspm").default;
if ("function" !== typeof run) {
throw Error("test must be run with 'mocha --delay ...'");
}
-console.log("typeof require (here)", typeof require);
-
-// We might need thins in the future ...
-global.nodeRequire = function (modulePath) {
- return require(modulePath);
-};
-
-global.require = global.nodeRequire;
+let emsc = require("../lib/emscripten/libwrapper.js");
-let data = fs.readFileSync("lib/emscripten/libwrapper.js");
-vm.runInThisContext(data);
-
-// Do it here, since it breaks 'require''
+// Do it here, since it breaks 'require'' for libwrapper
let System = require("systemjs");
+
System.config({
- defaultJSExtensions: true
+ defaultJSExtensions: true,
+ meta: {
+ './test/tests/taler.js': {
+ format: 'register'
+ },
+ './lib/wallet/*': {
+ format: 'register'
+ }
+ }
});
-let mod = System.newModule({Module: Module});
+instrument(System);
+
+let mod = System.newModule({Module: emsc});
let modName = System.normalizeSync(__dirname + "/../lib/emscripten/emsc");
console.log("registering", modName);
System.set(modName, mod);
-
System.import("./test/tests/taler.js")
.then((t) => {
t.declareTests(assert, context, it);
- run();
+ setTimeout(run, 1);
})
.catch((e) => {
console.error("failed to load module", e.stack);
});
-
-
diff --git a/test/tests/taler.ts b/test/tests/taler.ts
index 14130f9af..941e8284d 100644
--- a/test/tests/taler.ts
+++ b/test/tests/taler.ts
@@ -5,20 +5,9 @@ declare var HttpMockLib;
export function declareTests(assert, context, it) {
- it("works!", function() {
+ it("calls native emscripten code", function() {
let x = new Emsc.Amount({value: 42, fraction: 42, currency: "EUR"});
let j = x.toJson();
assert("value" in j);
});
-
-
- it("retries", function() {
- let m = new HttpMockLib();
- /*m.intercept()
- .matchUrlContains()
- .counterEquals(0)
- .count()
- .sen*/
- })
-
-} \ No newline at end of file
+}
diff --git a/tsconfig.json b/tsconfig.json
index 0ce2125c5..0e6337d86 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -33,4 +33,4 @@
"pages/confirm-create-reserve.tsx",
"test/tests/taler.ts"
]
-}
+} \ No newline at end of file