From 953cd9dc41ff3d52d23fe77f4ba3c18281e9d58f Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 14 Aug 2020 15:53:50 +0530 Subject: nuke some console.log statements, test wallet testing functionality in integration test --- packages/taler-integrationtests/src/harness.ts | 82 ++++++++++++++++---- .../src/test-wallettesting.ts | 90 ++++++++++++++++++++++ .../src/crypto/workers/cryptoApi.ts | 8 +- packages/taler-wallet-core/src/headless/helpers.ts | 4 +- packages/taler-wallet-core/src/i18n/index.ts | 5 +- packages/taler-wallet-core/src/index.ts | 71 ++++++++--------- .../taler-wallet-core/src/operations/exchanges.ts | 10 +-- packages/taler-wallet-core/src/operations/pay.ts | 14 ++-- .../taler-wallet-core/src/operations/recoup.ts | 13 ++-- .../taler-wallet-core/src/operations/refresh.ts | 8 +- .../taler-wallet-core/src/operations/refund.ts | 4 +- .../taler-wallet-core/src/operations/reserves.ts | 4 +- .../taler-wallet-core/src/operations/testing.ts | 78 +++++++++---------- packages/taler-wallet-core/src/operations/tip.ts | 17 ++-- .../src/operations/transactions.ts | 5 +- packages/taler-wallet-core/src/types/dbTypes.ts | 7 ++ .../taler-wallet-core/src/types/transactions.ts | 8 ++ .../taler-wallet-core/src/types/walletTypes.ts | 27 ++++++- .../taler-wallet-core/src/util/RequestThrottler.ts | 9 ++- packages/taler-wallet-core/src/util/query.ts | 26 ++++--- packages/taler-wallet-core/src/wallet.ts | 29 ++++--- .../src/browserWorkerEntry.ts | 10 +-- 22 files changed, 361 insertions(+), 168 deletions(-) create mode 100644 packages/taler-integrationtests/src/test-wallettesting.ts diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts index 8f9c540f6..d710feea4 100644 --- a/packages/taler-integrationtests/src/harness.ts +++ b/packages/taler-integrationtests/src/harness.ts @@ -29,6 +29,7 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import * as http from "http"; +import { deepStrictEqual } from "assert"; import { ChildProcess, spawn } from "child_process"; import { Configuration, @@ -53,6 +54,18 @@ import { ConfirmPayRequest, ConfirmPayResult, codecForConfirmPayResult, + IntegrationTestArgs, + TestPayArgs, + BalancesResponse, + codecForBalancesResponse, + encodeCrock, + getRandomBytes, + EddsaKeyPair, + eddsaGetPublic, + createEddsaKeyPair, + TransactionsResponse, + codecForTransactionsResponse, + WithdrawTestBalanceRequest, } from "taler-wallet-core"; import { URL } from "url"; import axios from "axios"; @@ -63,14 +76,6 @@ import { PostOrderResponse, MerchantOrderPrivateStatusResponse, } from "./merchantApiTypes"; -import { - EddsaKeyPair, - getRandomBytes, - encodeCrock, - eddsaGetPublic, - createEddsaKeyPair, -} from "taler-wallet-core/lib/crypto/talerCrypto"; -import { WithdrawalDetails } from "taler-wallet-core/lib/types/transactions"; const exec = util.promisify(require("child_process").exec); @@ -277,6 +282,10 @@ export class GlobalTestState { } } + assertDeepEqual(actual: any, expected: any): asserts actual is any { + deepStrictEqual(actual, expected); + } + assertAmountEquals( amtExpected: string | AmountJson, amtActual: string | AmountJson, @@ -521,6 +530,10 @@ export class BankService { config.setString("bank", "suggested_exchange_payto", exchangePayto); } + get baseUrl(): string { + return `http://localhost:${this.bankConfig.httpPort}/`; + } + async createExchangeAccount( accountName: string, password: string, @@ -890,11 +903,10 @@ export interface MerchantConfig { database: string; } - export interface PrivateOrderStatusQuery { - instance?: string, - orderId: string, - sessionId?: string, + instance?: string; + orderId: string; + sessionId?: string; } export class MerchantService { @@ -993,7 +1005,9 @@ export class MerchantService { }); } - async queryPrivateOrderStatus(query: PrivateOrderStatusQuery): Promise { + async queryPrivateOrderStatus( + query: PrivateOrderStatusQuery, + ): Promise { const reqUrl = new URL( `private/orders/${query.orderId}`, this.makeInstanceBaseUrl(query.instance), @@ -1215,6 +1229,46 @@ export class WalletCli { throw new OperationFailedError(resp.error); } + async getBalances(): Promise { + const resp = await this.apiRequest("getBalances", {}); + if (resp.type === "response") { + return codecForBalancesResponse().decode(resp.result); + } + throw new OperationFailedError(resp.error); + } + + async getTransactions(): Promise { + const resp = await this.apiRequest("getTransactions", {}); + if (resp.type === "response") { + return codecForTransactionsResponse().decode(resp.result); + } + throw new OperationFailedError(resp.error); + } + + async runIntegrationtest(args: IntegrationTestArgs): Promise { + const resp = await this.apiRequest("runIntegrationtest", args); + if (resp.type === "response") { + return; + } + throw new OperationFailedError(resp.error); + } + + async testPay(args: TestPayArgs): Promise { + const resp = await this.apiRequest("testPay", args); + if (resp.type === "response") { + return; + } + throw new OperationFailedError(resp.error); + } + + async withdrawTestBalance(args: WithdrawTestBalanceRequest): Promise { + const resp = await this.apiRequest("withdrawTestBalance", args); + if (resp.type === "response") { + return; + } + throw new OperationFailedError(resp.error); + } + async getWithdrawalDetailsForUri( req: GetWithdrawalDetailsForUriRequest, ): Promise { @@ -1222,6 +1276,6 @@ export class WalletCli { if (resp.type === "response") { return codecForWithdrawUriInfoResponse().decode(resp.result); } - throw new OperationFailedError(resp.error); + throw new OperationFailedError(resp.error); } } diff --git a/packages/taler-integrationtests/src/test-wallettesting.ts b/packages/taler-integrationtests/src/test-wallettesting.ts new file mode 100644 index 000000000..de1f1e228 --- /dev/null +++ b/packages/taler-integrationtests/src/test-wallettesting.ts @@ -0,0 +1,90 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + 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 + */ + +/** + * Integration test for the wallet testing functionality used by the exchange + * test cases. + */ + +/** + * Imports. + */ +import { runTest, GlobalTestState } from "./harness"; +import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; + +/** + * Run test for basic, bank-integrated withdrawal. + */ +runTest(async (t: GlobalTestState) => { + const { + wallet, + bank, + exchange, + merchant, + } = await createSimpleTestkudosEnvironment(t); + + await wallet.runIntegrationtest({ + amountToSpend: "TESTKUDOS:5", + amountToWithdraw: "TESTKUDOS:10", + bankBaseUrl: bank.baseUrl, + exchangeBaseUrl: exchange.baseUrl, + merchantApiKey: "sandbox", + merchantBaseUrl: merchant.makeInstanceBaseUrl(), + }); + + let txns = await wallet.getTransactions(); + console.log(JSON.stringify(txns, undefined, 2)); + let txTypes = txns.transactions.map((x) => x.type); + + t.assertDeepEqual(txTypes, [ + "withdrawal", + "payment", + "withdrawal", + "payment", + "refund", + "payment", + ]); + + wallet.deleteDatabase(); + + await wallet.withdrawTestBalance({ + amount: "TESTKUDOS:10", + bankBaseUrl: bank.baseUrl, + exchangeBaseUrl: exchange.baseUrl, + }); + + await wallet.runUntilDone(); + + await wallet.testPay({ + amount: "TESTKUDOS:5", + merchantApiKey: "sandbox", + merchantBaseUrl: merchant.makeInstanceBaseUrl(), + summary: "foo", + }); + + await wallet.runUntilDone(); + + txns = await wallet.getTransactions(); + console.log(JSON.stringify(txns, undefined, 2)); + txTypes = txns.transactions.map((x) => x.type); + + t.assertDeepEqual(txTypes, [ + "withdrawal", + "payment", + ]); + + await t.shutdown(); +}); diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 802a153ff..7d322877a 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -203,21 +203,21 @@ export class CryptoApi { handleWorkerError(ws: WorkerState, e: any): void { if (ws.currentWorkItem) { - console.error( + logger.error( `error in worker during ${ws.currentWorkItem.operation}`, e, ); } else { - console.error("error in worker", e); + logger.error("error in worker", e); } - console.error(e.message); + logger.error(e.message); try { if (ws.w) { ws.w.terminate(); ws.w = null; } } catch (e) { - console.error(e); + logger.error(e); } if (ws.currentWorkItem !== null) { ws.currentWorkItem.reject(e); diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts index 953493299..1141ad25e 100644 --- a/packages/taler-wallet-core/src/headless/helpers.ts +++ b/packages/taler-wallet-core/src/headless/helpers.ts @@ -105,7 +105,7 @@ export async function getDefaultNodeWallet( } const myVersionChange = (): Promise => { - console.error("version change requested, should not happen"); + logger.error("version change requested, should not happen"); throw Error(); }; @@ -119,7 +119,7 @@ export async function getDefaultNodeWallet( require("worker_threads"); workerFactory = new NodeThreadCryptoWorkerFactory(); } catch (e) { - console.log( + logger.warn( "worker threads not available, falling back to synchronous workers", ); workerFactory = new SynchronousCryptoWorkerFactory(); diff --git a/packages/taler-wallet-core/src/i18n/index.ts b/packages/taler-wallet-core/src/i18n/index.ts index b248d2666..c5b70b1fd 100644 --- a/packages/taler-wallet-core/src/i18n/index.ts +++ b/packages/taler-wallet-core/src/i18n/index.ts @@ -26,6 +26,9 @@ export { strings } from "./strings"; // @ts-ignore: no type decl for this library import * as jedLib from "jed"; +import { Logger } from "../util/logging"; + +const logger = new Logger("i18n/index.ts"); export let jed: any = undefined; @@ -38,7 +41,7 @@ export function setupI18n(lang: string): any { if (!strings[lang]) { lang = "en-US"; - console.log(`language ${lang} not found, defaulting to english`); + logger.warn(`language ${lang} not found, defaulting to english`); } jed = new jedLib.Jed(strings[lang]); } diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 1d5dc493b..193152abe 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -19,59 +19,48 @@ */ export { Wallet } from "./wallet"; + +// Errors +export { TalerErrorCode } from "./TalerErrorCode"; +export * from "./operations/errors"; + +// Utils for using the wallet under node +export { NodeHttpLib } from "./headless/NodeHttpLib"; export { getDefaultNodeWallet, DefaultNodeWalletArgs, } from "./headless/helpers"; -export { Amounts, AmountJson } from "./util/amounts"; -export { Logger } from "./util/logging"; - -export * from "./crypto/talerCrypto"; -export { - OperationFailedAndReportedError, - OperationFailedError, - makeErrorDetails, -} from "./operations/errors"; - -export * from "./types/walletTypes"; - -export * from "./types/talerTypes"; - -export * from "./util/taleruri"; - -export * from "./util/time"; - -export * from "./util/codec"; - -export { NodeHttpLib } from "./headless/NodeHttpLib"; - -export * from "./util/payto"; - -export * from "./util/testvectors"; export * from "./operations/versions"; -export type { CryptoWorker } from "./crypto/workers/cryptoWorker"; -export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi"; - -export * from "./util/http"; - -export { TalerErrorCode } from "./TalerErrorCode"; - -export * from "./util/query"; - -export { CryptoImplementation } from "./crypto/workers/cryptoImplementation"; - export * from "./db"; -export * from "./util/promiseUtils"; - +// Internationalization export * from "./i18n"; +// Crypto and crypto workers export * from "./crypto/workers/nodeThreadWorker"; +export { CryptoImplementation } from "./crypto/workers/cryptoImplementation"; +export type { CryptoWorker } from "./crypto/workers/cryptoWorker"; +export { CryptoWorkerFactory, CryptoApi } from "./crypto/workers/cryptoApi"; +export * from "./crypto/talerCrypto"; -export * from "./types/notifications"; - +// Util functionality +export { Amounts, AmountJson } from "./util/amounts"; +export { Logger } from "./util/logging"; export { Configuration } from "./util/talerconfig"; +export { URL } from "./util/url"; +export * from "./util/codec"; +export * from "./util/promiseUtils"; +export * from "./util/query"; +export * from "./util/http"; +export * from "./util/payto"; +export * from "./util/testvectors"; +export * from "./util/taleruri"; +export * from "./util/time"; -export { URL } from "./util/url"; \ No newline at end of file +// Types +export * from "./types/talerTypes"; +export * from "./types/walletTypes"; +export * from "./types/notifications"; +export * from "./types/transactions" diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index a7771f6d2..d40dd7883 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -175,7 +175,7 @@ async function updateExchangeWithKeys( async (tx) => { const r = await tx.get(Stores.exchanges, baseUrl); if (!r) { - console.warn(`exchange ${baseUrl} no longer present`); + logger.warn(`exchange ${baseUrl} no longer present`); return; } if (r.details) { @@ -222,10 +222,10 @@ async function updateExchangeWithKeys( if (oldDenom.isRevoked) { // We already marked the denomination as revoked, // this implies we revoked all coins - console.log("denom already revoked"); + logger.trace("denom already revoked"); continue; } - console.log("revoking denom", recoupInfo.h_denom_pub); + logger.trace("revoking denom", recoupInfo.h_denom_pub); oldDenom.isRevoked = true; await tx.put(Stores.denominations, oldDenom); const affectedCoins = await tx @@ -236,7 +236,7 @@ async function updateExchangeWithKeys( } } if (newlyRevokedCoinPubs.length != 0) { - console.log("recouping coins", newlyRevokedCoinPubs); + logger.trace("recouping coins", newlyRevokedCoinPubs); await createRecoupGroup(ws, tx, newlyRevokedCoinPubs); } }, @@ -246,7 +246,7 @@ async function updateExchangeWithKeys( // Asynchronously start recoup. This doesn't need to finish // for the exchange update to be considered finished. processRecoupGroup(ws, recoupGroupId).catch((e) => { - console.log("error while recouping coins:", e); + logger.error("error while recouping coins:", e); }); } diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 0576f7eab..c3dd6c6d3 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -353,7 +353,7 @@ async function getCoinsForPayment( throw Error("db inconsistent"); } if (denom.value.currency !== currency) { - console.warn( + logger.warn( `same pubkey for different currencies at exchange ${exchange.baseUrl}`, ); continue; @@ -539,7 +539,7 @@ async function incrementPurchasePayRetry( proposalId: string, err: OperationErrorDetails | undefined, ): Promise { - console.log("incrementing purchase pay retry with error", err); + logger.warn("incrementing purchase pay retry with error", err); await ws.db.runWithWriteTransaction([Stores.purchases], async (tx) => { const pr = await tx.get(Stores.purchases, proposalId); if (!pr) { @@ -693,7 +693,7 @@ async function processDownloadProposalImpl( fulfillmentUrl, ); if (differentPurchase) { - console.log("repurchase detected"); + logger.warn("repurchase detected"); p.proposalStatus = ProposalStatus.REPURCHASE; p.repurchaseProposalId = differentPurchase.proposalId; await tx.put(Stores.proposals, p); @@ -814,7 +814,7 @@ export async function submitPay( merchantPub, ); if (!valid) { - console.error("merchant payment signature invalid"); + logger.error("merchant payment signature invalid"); // FIXME: properly display error throw Error("merchant payment signature invalid"); } @@ -826,7 +826,7 @@ export async function submitPay( if (isFirst) { const ar = purchase.contractData.autoRefund; if (ar) { - console.log("auto_refund present"); + logger.info("auto_refund present"); purchase.refundStatusRequested = true; purchase.refundStatusRetryInfo = initRetryInfo(); purchase.lastRefundStatusError = undefined; @@ -899,7 +899,7 @@ export async function preparePayForUri( if (!existingProposalId) { throw Error("invalid proposal state"); } - console.log("using existing purchase for same product"); + logger.trace("using existing purchase for same product"); proposal = await ws.db.get(Stores.proposals, existingProposalId); if (!proposal) { throw Error("existing proposal is in wrong state"); @@ -907,7 +907,7 @@ export async function preparePayForUri( } const d = proposal.download; if (!d) { - console.error("bad proposal", proposal); + logger.error("bad proposal", proposal); throw Error("proposal is in invalid state"); } const contractData = d.contractData; diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index cc91ab0e9..f855a28cb 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -51,6 +51,9 @@ import { getTimestampNow } from "../util/time"; import { guardOperationException } from "./errors"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { URL } from "../util/url"; +import { Logger } from "../util/logging"; + +const logger = new Logger("operations/recoup.ts"); async function incrementRecoupRetry( ws: InternalWalletState, @@ -207,7 +210,7 @@ async function recoupWithdrawCoin( }); forceQueryReserve(ws, reserve.reservePub).catch((e) => { - console.log("re-querying reserve after recoup failed:", e); + logger.error("re-querying reserve after recoup failed:", e); }); } @@ -224,7 +227,7 @@ async function recoupRefreshCoin( const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); - console.log("making recoup request"); + logger.trace("making recoup request"); const resp = await ws.http.postJson(reqUrl.href, recoupRequest); const recoupConfirmation = await readSuccessResponseJsonOrThrow( @@ -270,7 +273,7 @@ async function recoupRefreshCoin( oldCoin.currentAmount, recoupGroup.oldAmountPerCoin[coinIdx], ).amount; - console.log( + logger.trace( "recoup: setting old coin amount to", Amounts.stringify(oldCoin.currentAmount), ); @@ -317,14 +320,12 @@ async function processRecoupGroupImpl( if (forceNow) { await resetRecoupGroupRetry(ws, recoupGroupId); } - console.log("in processRecoupGroupImpl"); const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId); if (!recoupGroup) { return; } - console.log(recoupGroup); if (recoupGroup.timestampFinished) { - console.log("recoup group finished"); + logger.trace("recoup group finished"); return; } const ps = recoupGroup.coinPubs.map((x, i) => diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 646bc2edf..52325281b 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -188,7 +188,7 @@ async function refreshCreateSession( } const r = Amounts.sub(c.currentAmount, refreshSession.amountRefreshInput); if (r.saturated) { - console.log("can't refresh coin, no amount left"); + logger.warn("can't refresh coin, no amount left"); return; } c.currentAmount = r.amount; @@ -387,7 +387,7 @@ async function refreshReveal( async (tx) => { const rg = await tx.get(Stores.refreshGroups, refreshGroupId); if (!rg) { - console.log("no refresh session found"); + logger.warn("no refresh session found"); return; } const rs = rg.refreshSessionPerCoin[coinIndex]; @@ -395,7 +395,7 @@ async function refreshReveal( return; } if (rs.finishedTimestamp) { - console.log("refresh session already finished"); + logger.warn("refresh session already finished"); return; } rs.finishedTimestamp = getTimestampNow(); @@ -417,7 +417,7 @@ async function refreshReveal( await tx.put(Stores.refreshGroups, rg); }, ); - console.log("refresh finished (end of reveal)"); + logger.trace("refresh finished (end of reveal)"); ws.notify({ type: NotificationType.RefreshRevealed, }); diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 9ee71012e..fb39aa638 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -50,7 +50,7 @@ import { AmountString, } from "../types/talerTypes"; import { guardOperationException } from "./errors"; -import { getTimestampNow } from "../util/time"; +import { getTimestampNow, Timestamp } from "../util/time"; import { Logger } from "../util/logging"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { TransactionHandle } from "../util/query"; @@ -142,6 +142,7 @@ async function applySuccessfulRefund( p.refunds[refundKey] = { type: RefundState.Applied, + obtainedTime: getTimestampNow(), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), refundFee: denom.feeRefund, @@ -191,6 +192,7 @@ async function storePendingRefund( p.refunds[refundKey] = { type: RefundState.Pending, + obtainedTime: getTimestampNow(), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), refundFee: denom.feeRefund, diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index 060226cab..fb525da45 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -178,7 +178,7 @@ export async function createReserve( const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange); const exchangeDetails = exchangeInfo.details; if (!exchangeDetails) { - console.log(exchangeDetails); + logger.trace(exchangeDetails); throw Error("exchange not updated"); } const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo); @@ -576,7 +576,7 @@ async function processReserveImpl( ): Promise { const reserve = await ws.db.get(Stores.reserves, reservePub); if (!reserve) { - console.log("not processing reserve: reserve does not exist"); + logger.trace("not processing reserve: reserve does not exist"); return; } if (!forceNow) { diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 629cd92ff..f1b2d98ff 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -269,11 +269,11 @@ async function makePayment( "taler://fulfillment-success/thx", ); - console.log("created order with orderId", orderResp.orderId); + logger.trace("created order with orderId", orderResp.orderId); let paymentStatus = await checkPayment(http, merchant, orderResp.orderId); - console.log("payment status", paymentStatus); + logger.trace("payment status", paymentStatus); const talerPayUri = paymentStatus.taler_pay_uri; if (!talerPayUri) { @@ -282,7 +282,7 @@ async function makePayment( const preparePayResult = await wallet.preparePayForUri(talerPayUri); - console.log("prepare pay result", preparePayResult); + logger.trace("prepare pay result", preparePayResult); if (preparePayResult.status != "payment-possible") { throw Error("payment not possible"); @@ -293,11 +293,11 @@ async function makePayment( undefined, ); - console.log("confirmPayResult", confirmPayResult); + logger.trace("confirmPayResult", confirmPayResult); paymentStatus = await checkPayment(http, merchant, orderResp.orderId); - console.log("payment status after wallet payment:", paymentStatus); + logger.trace("payment status after wallet payment:", paymentStatus); if (paymentStatus.order_status !== "paid") { throw Error("payment did not succeed"); @@ -318,26 +318,18 @@ export async function runIntegrationTest( const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend); const currency = parsedSpendAmount.currency; - const myHttpLib = new NodeHttpLib(); - myHttpLib.setThrottling(false); - - const myWallet = await getDefaultNodeWallet({ httpLib: myHttpLib }); - - myWallet.runRetryLoop().catch((e) => { - console.error("exception during retry loop:", e); - }); - logger.info("withdrawing test balance"); - await wallet.withdrawTestBalance( - args.amountToWithdraw, - args.bankBaseUrl, - args.exchangeBaseUrl, - ); + await wallet.withdrawTestBalance({ + amount: args.amountToWithdraw, + bankBaseUrl: args.bankBaseUrl, + exchangeBaseUrl: args.exchangeBaseUrl, + }); + await wallet.runUntilDone(); logger.info("done withdrawing test balance"); - const balance = await myWallet.getBalances(); + const balance = await wallet.getBalances(); - console.log(JSON.stringify(balance, null, 2)); + logger.trace(JSON.stringify(balance, null, 2)); const myMerchant: MerchantBackendInfo = { baseUrl: args.merchantBaseUrl, @@ -353,26 +345,26 @@ export async function runIntegrationTest( ); // Wait until the refresh is done - await myWallet.runUntilDone(); + await wallet.runUntilDone(); - console.log("withdrawing test balance for refund"); + logger.trace("withdrawing test balance for refund"); const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`); const spendAmountTwo = Amounts.parseOrThrow(`${currency}:7`); const refundAmount = Amounts.parseOrThrow(`${currency}:6`); const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`); - await myWallet.withdrawTestBalance( - Amounts.stringify(withdrawAmountTwo), - args.bankBaseUrl, - args.exchangeBaseUrl, - ); + await wallet.withdrawTestBalance({ + amount: Amounts.stringify(withdrawAmountTwo), + bankBaseUrl: args.bankBaseUrl, + exchangeBaseUrl: args.exchangeBaseUrl, + }); // Wait until the withdraw is done - await myWallet.runUntilDone(); + await wallet.runUntilDone(); const { orderId: refundOrderId } = await makePayment( http, - myWallet, + wallet, myMerchant, Amounts.stringify(spendAmountTwo), "order that will be refunded", @@ -386,22 +378,30 @@ export async function runIntegrationTest( Amounts.stringify(refundAmount), ); - console.log("refund URI", refundUri); + logger.trace("refund URI", refundUri); - await myWallet.applyRefund(refundUri); + await wallet.applyRefund(refundUri); + + logger.trace("integration test: applied refund"); // Wait until the refund is done - await myWallet.runUntilDone(); + await wallet.runUntilDone(); + + logger.trace("integration test: making payment after refund"); await makePayment( http, - myWallet, + wallet, myMerchant, Amounts.stringify(spendAmountThree), "payment after refund", ); - await myWallet.runUntilDone(); + logger.trace("integration test: make payment done"); + + await wallet.runUntilDone(); + + logger.trace("integration test: all done!"); } export async function testPay( @@ -409,8 +409,8 @@ export async function testPay( wallet: Wallet, args: TestPayArgs, ) { - console.log("creating order"); - const merchant = { apikey: args.apikey, baseUrl: args.merchant }; + logger.trace("creating order"); + const merchant = { apikey: args.merchantApiKey, baseUrl: args.merchantBaseUrl }; const orderResp = await createOrder( http, merchant, @@ -418,7 +418,7 @@ export async function testPay( args.summary, "taler://fulfillment-success/thank+you", ); - console.log("created new order with order ID", orderResp.orderId); + logger.trace("created new order with order ID", orderResp.orderId); const checkPayResp = await checkPayment(http, merchant, orderResp.orderId); const talerPayUri = checkPayResp.taler_pay_uri; if (!talerPayUri) { @@ -426,7 +426,7 @@ export async function testPay( process.exit(1); return; } - console.log("taler pay URI:", talerPayUri); + logger.trace("taler pay URI:", talerPayUri); const result = await wallet.preparePayForUri(talerPayUri); if (result.status !== PreparePayResultType.PaymentPossible) { throw Error(`unexpected prepare pay status: ${result.status}`); diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 84cfa570a..6dee9c87e 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -45,6 +45,9 @@ import { NotificationType } from "../types/notifications"; import { getTimestampNow } from "../util/time"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { URL } from "../util/url"; +import { Logger } from "../util/logging"; + +const logger = new Logger("operations/tip.ts"); export async function getTipStatus( ws: InternalWalletState, @@ -57,13 +60,13 @@ export async function getTipStatus( const tipStatusUrl = new URL("tip-pickup", res.merchantBaseUrl); tipStatusUrl.searchParams.set("tip_id", res.merchantTipId); - console.log("checking tip status from", tipStatusUrl.href); + logger.trace("checking tip status from", tipStatusUrl.href); const merchantResp = await ws.http.get(tipStatusUrl.href); const tipPickupStatus = await readSuccessResponseJsonOrThrow( merchantResp, codecForTipPickupGetResponse(), ); - console.log("status", tipPickupStatus); + logger.trace(`status ${tipPickupStatus}`); const amount = Amounts.parseOrThrow(tipPickupStatus.amount); @@ -191,7 +194,7 @@ async function processTipImpl( } if (tipRecord.pickedUp) { - console.log("tip already picked up"); + logger.warn("tip already picked up"); return; } @@ -230,7 +233,7 @@ async function processTipImpl( throw Error("invariant violated"); } - console.log("got planchets for tip!"); + logger.trace("got planchets for tip!"); // Planchets in the form that the merchant expects const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({ @@ -248,9 +251,9 @@ async function processTipImpl( if (merchantResp.status !== 200) { throw Error(`unexpected status ${merchantResp.status} for tip-pickup`); } - console.log("got merchant resp:", merchantResp); + logger.trace("got merchant resp:", merchantResp); } catch (e) { - console.log("tipping failed", e); + logger.warn("tipping failed", e); throw e; } @@ -331,7 +334,7 @@ export async function acceptTip( ): Promise { const tipRecord = await ws.db.get(Stores.tips, tipId); if (!tipRecord) { - console.log("tip not found"); + logger.error("tip not found"); return; } diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 5521dda90..e17dfac3a 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -25,7 +25,7 @@ import { RefundState, } from "../types/dbTypes"; import { Amounts, AmountJson } from "../util/amounts"; -import { timestampCmp } from "../util/time"; +import { timestampCmp, Timestamp } from "../util/time"; import { TransactionsRequest, TransactionsResponse, @@ -297,12 +297,13 @@ export async function getTransactions( if (!r0) { throw Error("invariant violated"); } + let ts: Timestamp; transactions.push({ type: TransactionType.Refund, info, refundedTransactionId: paymentTransactionId, transactionId: refundTransactionId, - timestamp: r0.executionTime, + timestamp: r0.obtainedTime, amountEffective: Amounts.stringify(amountEffective), amountRaw: Amounts.stringify(amountRaw), pending: false, diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index 3c4c2a250..26e636e48 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -1164,7 +1164,14 @@ export type WalletRefundItem = | WalletRefundAppliedItem; export interface WalletRefundItemCommon { + // Execution time as claimed by the merchant executionTime: Timestamp; + + /** + * Time when the wallet became aware of the refund. + */ + obtainedTime: Timestamp; + refundAmount: AmountJson; refundFee: AmountJson; diff --git a/packages/taler-wallet-core/src/types/transactions.ts b/packages/taler-wallet-core/src/types/transactions.ts index f3192e5a4..e40031499 100644 --- a/packages/taler-wallet-core/src/types/transactions.ts +++ b/packages/taler-wallet-core/src/types/transactions.ts @@ -31,6 +31,8 @@ import { buildCodecForObject, codecOptional, codecForString, + codecForList, + codecForAny, } from "../util/codec"; export interface TransactionsRequest { @@ -309,3 +311,9 @@ export const codecForTransactionsRequest = (): Codec => .property("currency", codecOptional(codecForString())) .property("search", codecOptional(codecForString())) .build("TransactionsRequest"); + +// FIXME: do full validation here! +export const codecForTransactionsResponse = (): Codec => + buildCodecForObject() + .property("transactions", codecForList(codecForAny())) + .build("TransactionsResponse"); \ No newline at end of file diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts index e64187e72..511d7766c 100644 --- a/packages/taler-wallet-core/src/types/walletTypes.ts +++ b/packages/taler-wallet-core/src/types/walletTypes.ts @@ -653,16 +653,16 @@ export interface GetExchangeTosResult { } export interface TestPayArgs { - merchant: string; - apikey: string; + merchantBaseUrl: string; + merchantApiKey: string; amount: string; summary: string; } export const codecForTestPayArgs = (): Codec => buildCodecForObject() - .property("merchant", codecForString()) - .property("apikey", codecForString()) + .property("merchantBaseUrl", codecForString()) + .property("merchantApiKey", codecForString()) .property("amount", codecForString()) .property("summary", codecForString()) .build("TestPayArgs"); @@ -829,3 +829,22 @@ export interface CoreApiResponseError { id: string; error: OperationErrorDetails; } + +export interface WithdrawTestBalanceRequest { + amount: string; + bankBaseUrl: string; + exchangeBaseUrl: string; +} + +export const withdrawTestBalanceDefaults = { + amount: "TESTKUDOS:10", + bankBaseUrl: "https://bank.test.taler.net/", + exchangeBaseUrl: "https://exchange.test.taler.net/", +}; + +export const codecForWithdrawTestBalance = (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("bankBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForString()) + .build("WithdrawTestBalanceRequest"); diff --git a/packages/taler-wallet-core/src/util/RequestThrottler.ts b/packages/taler-wallet-core/src/util/RequestThrottler.ts index 6f51a72bc..3b8f22f58 100644 --- a/packages/taler-wallet-core/src/util/RequestThrottler.ts +++ b/packages/taler-wallet-core/src/util/RequestThrottler.ts @@ -23,6 +23,9 @@ */ import { getTimestampNow, timestampDifference } from "../util/time"; import { URL } from "./url"; +import { Logger } from "./logging"; + +const logger = new Logger("RequestThrottler.ts"); /** * Maximum request per second, per origin. @@ -77,15 +80,15 @@ class OriginState { applyThrottle(): boolean { this.refill(); if (this.tokensSecond < 1) { - console.log("request throttled (per second limit exceeded)"); + logger.warn("request throttled (per second limit exceeded)"); return true; } if (this.tokensMinute < 1) { - console.log("request throttled (per minute limit exceeded)"); + logger.warn("request throttled (per minute limit exceeded)"); return true; } if (this.tokensHour < 1) { - console.log("request throttled (per hour limit exceeded)"); + logger.warn("request throttled (per hour limit exceeded)"); return true; } this.tokensSecond--; diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index f6e689f49..6571491a1 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -35,6 +35,10 @@ import { Event, IDBCursor, } from "idb-bridge"; +import { Logger } from "./logging"; + + +const logger = new Logger("query.ts"); /** * Exception that should be thrown by client code to abort a transaction. @@ -72,9 +76,9 @@ function requestToPromise(req: IDBRequest): Promise { resolve(req.result); }; req.onerror = () => { - console.log("error in DB request", req.error); + console.error("error in DB request", req.error); reject(req.error); - console.log("Request failed:", stack); + console.error("Request failed:", stack); }; }); } @@ -341,14 +345,14 @@ function runWithTransaction( resolve(funResult); }; tx.onerror = () => { - console.error("error in transaction"); - console.error(stack); + logger.error("error in transaction"); + logger.error(`${stack}`); }; tx.onabort = () => { if (tx.error) { - console.error("Transaction aborted with error:", tx.error); + logger.error("Transaction aborted with error:", tx.error); } else { - console.log("Trasaction aborted (no error)"); + logger.error("Trasaction aborted (no error)"); } reject(TransactionAbort); }; @@ -361,7 +365,7 @@ function runWithTransaction( }) .catch((e) => { if (e == TransactionAbort) { - console.info("aborting transaction"); + logger.trace("aborting transaction"); } else { console.error("Transaction failed:", e); console.error(stack); @@ -427,12 +431,12 @@ export function openDatabase( return new Promise((resolve, reject) => { const req = idbFactory.open(databaseName, databaseVersion); req.onerror = (e) => { - console.log("taler database error", e); + logger.error("taler database error", e); reject(new Error("database error")); }; req.onsuccess = (e) => { req.result.onversionchange = (evt: IDBVersionChangeEvent) => { - console.log( + logger.info( `handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`, ); req.result.close(); @@ -491,7 +495,7 @@ export class Database { importDatabase(dump: any): Promise { const db = this.db; - console.log("importing db", dump); + logger.info("importing db", dump); return new Promise((resolve, reject) => { const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite"); if (dump.stores) { @@ -501,7 +505,7 @@ export class Database { for (const key in dumpStore) { objects.push(dumpStore[key]); } - console.log(`importing ${objects.length} records into ${storeName}`); + logger.info(`importing ${objects.length} records into ${storeName}`); const store = tx.objectStore(storeName); for (const obj of objects) { store.put(obj); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 0b3e2ed60..4b309dde3 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -86,6 +86,10 @@ import { CoreApiResponse, codecForPreparePayRequest, codecForIntegrationTestArgs, + WithdrawTestBalanceRequest, + withdrawTestBalanceDefaults, + codecForWithdrawTestBalance, + codecForTestPayArgs, } from "./types/walletTypes"; import { Logger } from "./util/logging"; @@ -313,7 +317,7 @@ export class Wallet { } }); this.runRetryLoop().catch((e) => { - console.log("exception in wallet retry loop"); + logger.error("exception in wallet retry loop"); reject(e); }); }); @@ -377,7 +381,7 @@ export class Wallet { numPending, }); await Promise.race([timeout, this.latch.wait()]); - console.log("timeout done"); + logger.trace("timeout done"); } else { // FIXME: maybe be a bit smarter about executing these // operations in parallel? @@ -899,11 +903,9 @@ export class Wallet { } async withdrawTestBalance( - amount = "TESTKUDOS:10", - bankBaseUrl = "https://bank.test.taler.net/", - exchangeBaseUrl = "https://exchange.test.taler.net/", + req: WithdrawTestBalanceRequest, ): Promise { - await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl); + await withdrawTestBalance(this.ws, req.amount, req.bankBaseUrl, req.exchangeBaseUrl); } async runIntegrationtest(args: IntegrationTestArgs): Promise { @@ -924,7 +926,16 @@ export class Wallet { ): Promise> { switch (operation) { case "withdrawTestkudos": { - await this.withdrawTestBalance(); + await this.withdrawTestBalance({ + amount: "TESTKUDOS:10", + bankBaseUrl: "https://bank.test.taler.net/", + exchangeBaseUrl: "https://exchange.test.taler.net/", + }); + return {}; + } + case "withdrawTestBalance": { + const req = codecForWithdrawTestBalance().decode(payload); + await this.withdrawTestBalance(req); return {}; } case "runIntegrationtest": { @@ -933,8 +944,8 @@ export class Wallet { return {} } case "testPay": { - const req = codecForIntegrationTestArgs().decode(payload); - await this.runIntegrationtest(req); + const req = codecForTestPayArgs().decode(payload); + await this.testPay(req); return {} } case "getTransactions": { diff --git a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts index 77c38fda9..cb13582a2 100644 --- a/packages/taler-wallet-webextension/src/browserWorkerEntry.ts +++ b/packages/taler-wallet-webextension/src/browserWorkerEntry.ts @@ -22,7 +22,9 @@ * Imports. */ -import { CryptoImplementation } from "taler-wallet-core"; +import { CryptoImplementation, Logger } from "taler-wallet-core"; + +const logger = new Logger("browserWorkerEntry.ts"); const worker: Worker = (self as any) as Worker; @@ -42,7 +44,7 @@ async function handleRequest( const result = (impl as any)[operation](...args); worker.postMessage({ result, id }); } catch (e) { - console.log("error during operation", e); + logger.error("error during operation", e); return; } } @@ -64,10 +66,6 @@ worker.onmessage = (msg: MessageEvent) => { return; } - if (CryptoImplementation.enableTracing) { - console.log("onmessage with", operation); - } - handleRequest(operation, id, args).catch((e) => { console.error("error in browsere worker", e); }); -- cgit v1.2.3