taler-typescript-core

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

commit ec559f2064a4fa8a9b2b4473d9db58ff27a54a39
parent f182fcceb124d47ff86e13801137085d0092182e
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:44 +0200

wallet-core: retain shared currency metadata

Diffstat:
Mpackages/taler-wallet-core/src/requests.test.ts | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/requests.ts | 11+++++++----
2 files changed, 72 insertions(+), 4 deletions(-)

diff --git a/packages/taler-wallet-core/src/requests.test.ts b/packages/taler-wallet-core/src/requests.test.ts @@ -47,6 +47,7 @@ import { handleGetDefaultExchanges, handleHintApplicationResumed, handleListWithdrawalExchangeCandidates, + handleRemoveGlobalCurrencyExchange, setCoinSuspended, } from "./requests.js"; import { WalletApiOperation } from "./wallet-api-types.js"; @@ -171,6 +172,70 @@ test("application-resumed hint reports DB failures independently", async () => { ); }); +test("removing one global exchange retains shared currency metadata", async () => { + const exchanges = [ + { + id: 1, + currency: "TESTKUDOS", + exchangeBaseUrl: "https://one.example/", + exchangeMasterPub: "master-one", + }, + { + id: 2, + currency: "TESTKUDOS", + exchangeBaseUrl: "https://two.example/", + exchangeMasterPub: "master-two", + }, + ]; + let currencyInfoDeletes = 0; + const tx = { + async getGlobalCurrencyExchange( + currency: string, + exchangeBaseUrl: string, + exchangeMasterPub: string, + ) { + return exchanges.find( + (x) => + x.currency === currency && + x.exchangeBaseUrl === exchangeBaseUrl && + x.exchangeMasterPub === exchangeMasterPub, + ); + }, + async deleteGlobalCurrencyExchange(id: number): Promise<void> { + const index = exchanges.findIndex((x) => x.id === id); + if (index >= 0) exchanges.splice(index, 1); + }, + async listGlobalCurrencyExchanges() { + return exchanges; + }, + async deleteCurrencyInfo(): Promise<void> { + currencyInfoDeletes++; + }, + } as unknown as WalletDbTransaction; + const wex = { + async runWalletDbTx<T>( + f: (tx: WalletDbTransaction) => Promise<T>, + ): Promise<T> { + return f(tx); + }, + ws: { exchangeCache: { clear() {} } }, + } as WalletExecutionContext; + + await handleRemoveGlobalCurrencyExchange(wex, { + currency: "TESTKUDOS", + exchangeBaseUrl: "https://one.example/", + exchangeMasterPub: "master-one", + }); + assert.strictEqual(currencyInfoDeletes, 0); + + await handleRemoveGlobalCurrencyExchange(wex, { + currency: "TESTKUDOS", + exchangeBaseUrl: "https://two.example/", + exchangeMasterPub: "master-two", + }); + assert.strictEqual(currencyInfoDeletes, 1); +}); + function makeCoinSuspensionContext( status: CoinStatus, freshCoinCount: number, diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts @@ -1745,11 +1745,14 @@ export async function handleRemoveGlobalCurrencyExchange( !!existingRec.id, `no global exchange for ${req.currency}`, ); - await tx.deleteCurrencyInfo({ - type: ScopeType.Global, - currency: req.currency, - }); await tx.deleteGlobalCurrencyExchange(existingRec.id); + const remainingExchanges = await tx.listGlobalCurrencyExchanges(); + if (!remainingExchanges.some((x) => x.currency === req.currency)) { + await tx.deleteCurrencyInfo({ + type: ScopeType.Global, + currency: req.currency, + }); + } }); return {}; }