taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit dfdcc1ff64e2a5d5c1b1891728a878d382438315
parent b445019624df3202ff392d8770bd41c13d5e1606
Author: Florian Dold <florian@dold.me>
Date:   Tue, 14 Jul 2026 23:14:43 +0200

wallet-core: new tc dev experiment option for subsequent errors on same URL

Diffstat:
Mpackages/taler-wallet-core/src/dev-experiments.ts | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mpackages/taler-wallet-core/src/wallet.ts | 56+-------------------------------------------------------
2 files changed, 104 insertions(+), 63 deletions(-)

diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts @@ -77,7 +77,6 @@ import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js"; import { RefreshTransactionContext } from "./refresh.js"; import { rematerializeTransactions } from "./transactions.js"; import { - DevExperimentState, WalletExecutionContext, handleAddGlobalCurrencyExchange, handleRemoveGlobalCurrencyExchange, @@ -86,6 +85,74 @@ import { WithdrawTransactionContext } from "./withdraw.js"; const logger = new Logger("dev-experiments.ts"); +export interface DevExperimentState { + blockRefreshes?: boolean; + /** Pretend that exchanges have no fees.*/ + pretendNoFees?: boolean; + /** Pretend exchange has no withdrawable denoms. */ + pretendNoDenoms?: boolean; + pretendPostWopFailed?: boolean; + merchantDepositInsufficient?: boolean; + /** Map from base URL to faked version for /config or /keys */ + fakeProtoVer?: Map< + string, + { + fakeVer: string; + } + >; + + /** Map from base URL to faked version for /config or /keys */ + fakeMasterPub?: Map< + string, + { + fakeMasterPub: string; + } + >; + + fakeShoppingUrl?: string; + + flagDisablePeerPayments?: boolean; + + /** Set of exchanges to fake disabled direct deposits for. */ + flagDisableDirectDeposits?: Set<string>; + + fakeChfBban?: boolean; + + fakeDemoShortcuts?: AmountString[]; + + fakeDefaultExchangeDemo?: boolean; + + blockPayResponse?: boolean; + + /** Migration test for confirmPay */ + flagConfirmPayNoWait?: boolean; + + tc?: { + dropRequestProbability?: number; + dropResponseProbability?: number; + /** + * Fake a 500 response *after* the request has been sent + * and the real response has been received. + */ + fake500Probability?: number; + /** + * Fake a 500 for the number of specified times + * for the same URL. + * + * The counter is reset whenever the star-tc dev experiment + * is re-scanned. + */ + fake500Count?: number; + delayResponseMs?: number; + + /** + * Mapping from $METHOD:$URL to a counter indicating + * how many times we faked a 500 response. + */ + fake500Map: Map<string, number>; + }; +} + /** * Apply a dev experiment to the wallet database / state. */ @@ -183,11 +250,23 @@ export async function applyDevExperiment( } throw Error("unsupported parameter"); }; + const maybeIntParam = (name: string) => { + const val = parsedUri.query?.[name]; + if (val == null) { + return undefined; + } + if (typeof val === "string") { + return Number.parseInt(val, 10); + } + throw Error("unsupported parameter"); + }; wex.ws.devExperimentState.tc = { delayResponseMs: maybeFloatParam("delay_resp"), dropRequestProbability: maybeFloatParam("drop_req"), dropResponseProbability: maybeFloatParam("drop_resp"), fake500Probability: maybeFloatParam("fake_500"), + fake500Count: maybeIntParam("fake_500_count"), + fake500Map: new Map(), }; return; } @@ -877,7 +956,7 @@ export class DevExperimentHttpLib implements HttpRequestLibrary { `Request timed out after ${5000} ms`, ); } - const resp = await this.underlyingLib.fetch(url, opt); + let resp = await this.underlyingLib.fetch(url, opt); if (evalFailProbability(this.devExperimentState.tc?.fake500Probability)) { return mockInternalServerError(resp); } @@ -895,12 +974,28 @@ export class DevExperimentHttpLib implements HttpRequestLibrary { `Request timed out after ${5000} ms`, ); } - // Delay response if requested - { - const delayMs = this.devExperimentState.tc?.delayResponseMs; - if (delayMs != null) { - logger.info(`delaying response by ${delayMs}ms`); - await waitMs(delayMs); + + const tc = this.devExperimentState.tc; + + if (tc) { + const fake500Count = tc.fake500Count ?? 0; + if (fake500Count > 0) { + const reqKey = `${(opt?.method ?? "GET").toUpperCase()}:${url}`; + const oldCount = tc.fake500Map.get(reqKey) ?? 0; + logger.info(`count ${oldCount}/${fake500Count} for ${reqKey}`); + tc.fake500Map.set(reqKey, oldCount + 1); + if (oldCount <= fake500Count) { + resp = mockInternalServerError(resp); + } + } + + // Delay response if requested + { + const delayMs = this.devExperimentState.tc?.delayResponseMs; + if (delayMs != null) { + logger.info(`delaying response by ${delayMs}ms`); + await waitMs(delayMs); + } } } return resp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -331,7 +331,7 @@ import { } from "./denominations.js"; import { UnverifiedDenomError } from "./denomSelection.js"; import { checkDepositGroup, createDepositGroup } from "./deposits.js"; -import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js"; +import { DevExperimentHttpLib, DevExperimentState, applyDevExperiment } from "./dev-experiments.js"; import { handleGetDonau, handleGetDonauStatements, @@ -3187,60 +3187,6 @@ export class Wallet { } } -export interface DevExperimentState { - blockRefreshes?: boolean; - /** Pretend that exchanges have no fees.*/ - pretendNoFees?: boolean; - /** Pretend exchange has no withdrawable denoms. */ - pretendNoDenoms?: boolean; - pretendPostWopFailed?: boolean; - merchantDepositInsufficient?: boolean; - /** Map from base URL to faked version for /config or /keys */ - fakeProtoVer?: Map< - string, - { - fakeVer: string; - } - >; - - /** Map from base URL to faked version for /config or /keys */ - fakeMasterPub?: Map< - string, - { - fakeMasterPub: string; - } - >; - - fakeShoppingUrl?: string; - - flagDisablePeerPayments?: boolean; - - /** Set of exchanges to fake disabled direct deposits for. */ - flagDisableDirectDeposits?: Set<string>; - - fakeChfBban?: boolean; - - fakeDemoShortcuts?: AmountString[]; - - fakeDefaultExchangeDemo?: boolean; - - blockPayResponse?: boolean; - - /** Migration test for confirmPay */ - flagConfirmPayNoWait?: boolean; - - tc?: { - dropRequestProbability?: number; - dropResponseProbability?: number; - /** - * Fake a 500 response *after* the request has been sent - * and the real response has been received. - */ - fake500Probability?: number; - delayResponseMs?: number; - }; -} - export class Cache<T> { private map: Map<string, [AbsoluteTime, T]> = new Map();