taler-typescript-core

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

commit 9713967b3f0d4e1d0a17f86978cc024528738877
parent ec95635e50bb9669a6ffc4e7e0998cfbde5b112c
Author: Florian Dold <florian@dold.me>
Date:   Tue, 23 Jun 2026 22:05:41 +0200

harness,wallet-core: simplify/fix timetravel-autorefresh

The new testingWaitBalance request and the waitAutoRefresh parameter for
the testingWaitExchangeReady request make the test more robust and
simpler.

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts | 34++++++++++++++++++++--------------
Mpackages/taler-util/src/types-taler-wallet.ts | 15+++++++++++++++
Mpackages/taler-wallet-core/src/exchanges.ts | 23+++++++++++++++++++++++
Mpackages/taler-wallet-core/src/testing.ts | 25++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/wallet-api-types.ts | 12++++++++++++
Mpackages/taler-wallet-core/src/wallet.ts | 16++++++++++++++++
6 files changed, 110 insertions(+), 15 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts @@ -19,11 +19,13 @@ */ import { Duration, + ExchangeUpdateStatus, NotificationType, PreparePayResultType, TalerCorebankApiClient, TalerMerchantInstanceHttpClient, TransactionMajorState, + j2s, succeedOrThrow, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -160,10 +162,16 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) { t.logStep("wait"); await wres.withdrawalFinishedCond; const exchangeUpdated1Cond = walletClient.waitForNotificationCond((x) => { - return ( + if ( x.type === NotificationType.ExchangeStateTransition && x.exchangeBaseUrl === exchange.baseUrl - ); + ) { + console.log(`got exchange notification: ${j2s(x)}`); + return ( + x.newExchangeState?.exchangeUpdateStatus === ExchangeUpdateStatus.Ready + ); + } + return false; }); t.logStep("waiting tx"); @@ -212,12 +220,6 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) { t.assertAmountEquals(balance.balances[0].available, "TESTKUDOS:35"); } - const exchangeUpdated2Cond = walletClient.waitForNotificationCond( - (x) => - x.type === NotificationType.ExchangeStateTransition && - x.exchangeBaseUrl === exchange.baseUrl, - ); - // Travel into the future, the deposit expiration is two years // into the future. t.logStep("applying second time travel"); @@ -232,12 +234,16 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) { // The time travel should cause exchanges to update. t.logStep("The time travel should cause exchanges to update."); - await exchangeUpdated2Cond; - await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); - { - const balance = await walletClient.call(WalletApiOperation.GetBalances, {}); - t.assertAmountEquals(balance.balances[0].available, "TESTKUDOS:35"); - } + + await walletClient.call(WalletApiOperation.TestingWaitExchangeReady, { + exchangeBaseUrl: exchange.baseUrl, + waitAutoRefresh: true, + }); + + await walletClient.call(WalletApiOperation.TestingWaitBalance, { + type: "material", + amount: "TESTKUDOS:35", + }); // At this point, the original coins should've been refreshed. // It would be too late to refresh them now, as we're past diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -3847,6 +3847,11 @@ export interface TestingWaitExchangeReadyRequest { * Force waiting until an update really happened. */ forceUpdate?: boolean; + /** + * Only consider the exchange as ready if the + * next auto-refresh is scheduled for the future. + */ + waitAutoRefresh?: boolean; } export const codecForTestingWaitExchangeReadyRequest = @@ -3855,6 +3860,7 @@ export const codecForTestingWaitExchangeReadyRequest = .property("exchangeBaseUrl", codecForString()) .property("noBail", codecOptional(codecForBoolean())) .property("forceUpdate", codecOptional(codecForBoolean())) + .property("waitAutoRefresh", codecOptional(codecForBoolean())) .build("TestingWaitExchangeReadyRequest"); export interface TransactionStatePattern { @@ -3862,6 +3868,15 @@ export interface TransactionStatePattern { minor?: TransactionMinorState | TransactionStateWildcard; } +export interface TestingWaitBalanceRequest { + type: "material" | "available"; + amount: AmountString; +} + +// No validation, test-only request. +export const codecForTestingWaitBalanceRequest: () => Codec<TestingWaitBalanceRequest> = + codecForAny; + export interface TestingWaitTransactionRequest { transactionId: TransactionIdStr; diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -1363,6 +1363,7 @@ export async function waitReadyExchange( forceUpdate?: boolean; expectedMasterPub?: string; noBail?: boolean; + waitAutoRefresh?: boolean; } = {}, ): Promise<ReadyExchangeSummary> { logger.trace(`waiting for exchange ${exchangeBaseUrl} to become ready`); @@ -1484,6 +1485,21 @@ export async function waitReadyExchange( return false; } + if (options.waitAutoRefresh) { + logger.info(`checking for exchange auto-refresh status`); + if ( + AbsoluteTime.isExpired( + timestampAbsoluteFromDb(exchange.nextRefreshCheckStamp), + ) + ) { + // Next planned refresh check is in the past, we need to wait. + logger.info( + `exchange ${exchange.baseUrl} not ready, waiting for auto-refresh`, + ); + return false; + } + } + if (!exchangeDetails) { throw Error("invariant failed"); } @@ -2302,6 +2318,13 @@ async function doExchangeAutoRefresh( ); wex.ws.exchangeCache.clear(); await tx.exchanges.put(exchange); + const st = getExchangeState(exchange); + tx.notify({ + type: NotificationType.ExchangeStateTransition, + exchangeBaseUrl, + newExchangeState: st, + oldExchangeState: st, + }); }); } diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts @@ -42,6 +42,7 @@ import { parsePaytoUri, PreparePayResultType, TalerCorebankApiClient, + TestingWaitBalanceRequest, TestingWaitTransactionRequest, TestPayArgs, TestPayResult, @@ -58,7 +59,7 @@ import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-util/http"; -import { getBalances } from "./balance.js"; +import { getBalanceDetail, getBalances } from "./balance.js"; import { genericWaitForState } from "./common.js"; import { createDepositGroup } from "./deposits.js"; import { fetchFreshExchange } from "./exchanges.js"; @@ -982,3 +983,25 @@ export async function testPay( numCoins: purchase.payInfo?.payCoinSelection?.coinContributions.length ?? 0, }; } + +export async function testingWaitBalance( + wex: WalletExecutionContext, + req: TestingWaitBalanceRequest, +): Promise<void> { + await genericWaitForState(wex, { + filterNotification(notif): boolean { + return notif.type === NotificationType.BalanceChange; + }, + async checkState(): Promise<boolean> { + const bal = await getBalanceDetail(wex, { + currency: Amounts.currencyOf(req.amount), + }); + logger.info(`current balance: ${j2s(bal)}`); + if (req.type === "material") { + return Amounts.cmp(bal.balanceMaterial, req.amount) == 0; + } else { + throw Error("not supported"); + } + }, + }); +} diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -186,6 +186,7 @@ import { TestingGetReserveHistoryRequest, TestingPlanMigrateExchangeBaseUrlRequest, TestingSetTimetravelRequest, + TestingWaitBalanceRequest, TestingWaitExchangeReadyRequest, TestingWaitExchangeStateRequest, TestingWaitTransactionRequest, @@ -371,6 +372,7 @@ export enum WalletApiOperation { TestingWaitExchangeState = "testingWaitExchangeState", TestingWaitExchangeReady = "testingWaitExchangeReady", TestingWaitTasksDone = "testingWaitTasksDone", + TestingWaitBalance = "testingWaitBalance", TestingGetDbStats = "testingGetDbStats", TestingSetTimetravel = "testingSetTimetravel", TestingGetDenomStats = "testingGetDenomStats", @@ -1525,6 +1527,15 @@ export type TestingWaitRefreshesFinalOp = { }; /** + * Wait until a balance has reached the desired value. + */ +export type TestingWaitBalanceOp = { + op: WalletApiOperation.TestingWaitBalance; + request: TestingWaitBalanceRequest; + response: EmptyObject; +}; + +/** * Wait until a transaction is in a particular state. */ export type TestingWaitTransactionStateOp = { @@ -1716,6 +1727,7 @@ export type WalletOperations = { [WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp; [WalletApiOperation.TestingGetDbStats]: TestingGetDbStats; [WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp; + [WalletApiOperation.TestingWaitBalance]: TestingWaitBalanceOp; [WalletApiOperation.TestingWaitExchangeState]: TestingWaitExchangeStateOp; [WalletApiOperation.TestingWaitExchangeReady]: TestingWaitExchangeReadyOp; [WalletApiOperation.TestingWaitTasksDone]: TestingWaitTasksDoneOp; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -150,6 +150,7 @@ import { TestingGetFlightRecordsResponse, TestingGetReserveHistoryRequest, TestingSetTimetravelRequest, + TestingWaitBalanceRequest, TestingWaitExchangeReadyRequest, TimerAPI, TimerGroup, @@ -263,6 +264,7 @@ import { codecForTestingGetReserveHistoryRequest, codecForTestingPlanMigrateExchangeBaseUrlRequest, codecForTestingSetTimetravelRequest, + codecForTestingWaitBalanceRequest, codecForTestingWaitExchangeReadyRequest, codecForTestingWaitWalletKycRequest, codecForTransactionByIdRequest, @@ -418,6 +420,7 @@ import { runIntegrationTest, runIntegrationTest2, testPay, + testingWaitBalance, waitTasksDone, waitTransactionState, waitUntilAllTransactionsFinal, @@ -2121,10 +2124,19 @@ export async function handleTestingWaitExchangeReady( await waitReadyExchange(wex, req.exchangeBaseUrl, { noBail: req.noBail, forceUpdate: req.forceUpdate, + waitAutoRefresh: req.waitAutoRefresh }); return {}; } +export async function handleTestingWaitBalance( + wex: WalletExecutionContext, + req: TestingWaitBalanceRequest, +): Promise<EmptyObject> { + await testingWaitBalance(wex, req); + return {}; +} + export async function handleGetPerformanceStats( wex: WalletExecutionContext, req: GetPerformanceStatsRequest, @@ -2147,6 +2159,10 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = { codec: codecForTestingWaitExchangeReadyRequest(), handler: handleTestingWaitExchangeReady, }, + [WalletApiOperation.TestingWaitBalance]: { + codec: codecForTestingWaitBalanceRequest(), + handler: handleTestingWaitBalance, + }, [WalletApiOperation.TestingCorruptWithdrawalCoinSel]: { codec: codecForTestingCorruptWithdrawalCoinSelRequest(), handler: handleTestingCorruptWithdrawalCoinSel,