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