nuke some console.log statements, test wallet testing functionality in integration test

This commit is contained in:
Florian Dold 2020-08-14 15:53:50 +05:30
parent e3850158c2
commit 953cd9dc41
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
22 changed files with 361 additions and 168 deletions

View File

@ -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<MerchantOrderPrivateStatusResponse> {
async queryPrivateOrderStatus(
query: PrivateOrderStatusQuery,
): Promise<MerchantOrderPrivateStatusResponse> {
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<BalancesResponse> {
const resp = await this.apiRequest("getBalances", {});
if (resp.type === "response") {
return codecForBalancesResponse().decode(resp.result);
}
throw new OperationFailedError(resp.error);
}
async getTransactions(): Promise<TransactionsResponse> {
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<void> {
const resp = await this.apiRequest("runIntegrationtest", args);
if (resp.type === "response") {
return;
}
throw new OperationFailedError(resp.error);
}
async testPay(args: TestPayArgs): Promise<void> {
const resp = await this.apiRequest("testPay", args);
if (resp.type === "response") {
return;
}
throw new OperationFailedError(resp.error);
}
async withdrawTestBalance(args: WithdrawTestBalanceRequest): Promise<void> {
const resp = await this.apiRequest("withdrawTestBalance", args);
if (resp.type === "response") {
return;
}
throw new OperationFailedError(resp.error);
}
async getWithdrawalDetailsForUri(
req: GetWithdrawalDetailsForUriRequest,
): Promise<WithdrawUriInfoResponse> {
@ -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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* 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();
});

View File

@ -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);

View File

@ -105,7 +105,7 @@ export async function getDefaultNodeWallet(
}
const myVersionChange = (): Promise<void> => {
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();

View File

@ -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]);
}

View File

@ -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";
// Types
export * from "./types/talerTypes";
export * from "./types/walletTypes";
export * from "./types/notifications";
export * from "./types/transactions"

View File

@ -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);
});
}

View File

@ -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<void> {
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;

View File

@ -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) =>

View File

@ -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,
});

View File

@ -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,

View File

@ -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<void> {
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) {

View File

@ -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}`);

View File

@ -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<void> {
const tipRecord = await ws.db.get(Stores.tips, tipId);
if (!tipRecord) {
console.log("tip not found");
logger.error("tip not found");
return;
}

View File

@ -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,

View File

@ -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;

View File

@ -31,6 +31,8 @@ import {
buildCodecForObject,
codecOptional,
codecForString,
codecForList,
codecForAny,
} from "../util/codec";
export interface TransactionsRequest {
@ -309,3 +311,9 @@ export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
.property("currency", codecOptional(codecForString()))
.property("search", codecOptional(codecForString()))
.build("TransactionsRequest");
// FIXME: do full validation here!
export const codecForTransactionsResponse = (): Codec<TransactionsResponse> =>
buildCodecForObject<TransactionsResponse>()
.property("transactions", codecForList(codecForAny()))
.build("TransactionsResponse");

View File

@ -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<TestPayArgs> =>
buildCodecForObject<TestPayArgs>()
.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<WithdrawTestBalanceRequest> =>
buildCodecForObject<WithdrawTestBalanceRequest>()
.property("amount", codecForString())
.property("bankBaseUrl", codecForString())
.property("exchangeBaseUrl", codecForString())
.build("WithdrawTestBalanceRequest");

View File

@ -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--;

View File

@ -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<any> {
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<T>(
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<T>(
})
.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<IDBDatabase>((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<void> {
const db = this.db;
console.log("importing db", dump);
logger.info("importing db", dump);
return new Promise<void>((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);

View File

@ -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<void> {
await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl);
await withdrawTestBalance(this.ws, req.amount, req.bankBaseUrl, req.exchangeBaseUrl);
}
async runIntegrationtest(args: IntegrationTestArgs): Promise<void> {
@ -924,7 +926,16 @@ export class Wallet {
): Promise<Record<string, any>> {
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": {

View File

@ -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);
});