diff options
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-wallet-refresh.ts | 60 | ||||
-rw-r--r-- | packages/taler-harness/tsconfig.json | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/index.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pay-peer-pull-debit.ts | 50 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/refresh.ts | 85 |
5 files changed, 127 insertions, 72 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts b/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts index b86dfadcf..f1c544a4e 100644 --- a/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts +++ b/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts @@ -20,12 +20,15 @@ import { AmountString, NotificationType, + TransactionIdStr, TransactionMajorState, TransactionType, j2s, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { parseTransactionIdentifier } from "../../../taler-wallet-core/src/transactions.js"; +import { + WalletApiOperation, + parseTransactionIdentifier, +} from "@gnu-taler/taler-wallet-core"; import { GlobalTestState, generateRandomPayto } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, @@ -139,6 +142,59 @@ export async function runWalletRefreshTest(t: GlobalTestState) { bal2.balances[0].available, ); } + + const wres = await withdrawViaBankV2(t, { + walletClient, + bank, + exchange, + amount: "TESTKUDOS:20", + }); + + await wres.withdrawalFinishedCond; + + // Test failing a refresh transaction + { + await exchange.stop(); + + let refreshTransactionId: TransactionIdStr | undefined = undefined; + + const refreshCreatedCond = walletClient.waitForNotificationCond((x) => { + if ( + x.type === NotificationType.TransactionStateTransition && + parseTransactionIdentifier(x.transactionId)?.tag === + TransactionType.Refresh + ) { + refreshTransactionId = x.transactionId as TransactionIdStr; + return true; + } + return false; + }); + + const depositGroupResult = await walletClient.client.call( + WalletApiOperation.CreateDepositGroup, + { + amount: "TESTKUDOS:10.5" as AmountString, + depositPaytoUri: generateRandomPayto("foo"), + }, + ); + + await refreshCreatedCond; + + t.assertTrue(!!refreshTransactionId); + + await walletClient.call(WalletApiOperation.FailTransaction, { + transactionId: refreshTransactionId, + }); + + const txn = await walletClient.call(WalletApiOperation.GetTransactionById, { + transactionId: refreshTransactionId, + }); + + t.assertDeepEqual(txn.type, TransactionType.Refresh); + t.assertDeepEqual(txn.txState.major, TransactionMajorState.Failed); + + t.assertTrue(!!refreshTransactionId); + } } runWalletRefreshTest.suites = ["wallet"]; diff --git a/packages/taler-harness/tsconfig.json b/packages/taler-harness/tsconfig.json index 3d0b501b3..0453aaff0 100644 --- a/packages/taler-harness/tsconfig.json +++ b/packages/taler-harness/tsconfig.json @@ -21,7 +21,7 @@ "baseUrl": "./src", "typeRoots": ["./node_modules/@types"] }, - "include": ["src/**/*", "../taler-util/src/merchant-api-types.ts"], + "include": ["src/**/*"], "references": [ { "path": "../taler-wallet-core/" diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index fa984fc8f..fe2d3af15 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -32,6 +32,8 @@ export * from "./versions.js"; export * from "./wallet-api-types.js"; export * from "./wallet.js"; +export { parseTransactionIdentifier } from "./transactions.js"; + export { createPairTimeline } from "./denominations.js"; // FIXME: Should these really be exported?! diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts index 7348a30ce..2418f08da 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -26,7 +26,6 @@ import { AcceptPeerPullPaymentResponse, Amounts, - CancellationToken, CoinRefreshRequest, ConfirmPeerPullDebitRequest, ContractTermsUtil, @@ -95,7 +94,7 @@ import { notifyTransition, parseTransactionIdentifier, } from "./transactions.js"; -import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; +import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("pay-peer-pull-debit.ts"); @@ -552,15 +551,9 @@ export async function processPeerPullDebit( switch (peerPullInc.status) { case PeerPullDebitRecordStatus.PendingDeposit: - return await processPeerPullDebitPendingDeposit( - wex, - peerPullInc, - ); + return await processPeerPullDebitPendingDeposit(wex, peerPullInc); case PeerPullDebitRecordStatus.AbortingRefresh: - return await processPeerPullDebitAbortingRefresh( - wex, - peerPullInc, - ); + return await processPeerPullDebitAbortingRefresh(wex, peerPullInc); } return TaskRunResult.finished(); } @@ -791,23 +784,26 @@ export async function preparePeerPullDebit( coinSelRes.result.coins, ); - await wex.db.runReadWriteTx(["peerPullDebit", "contractTerms"], async (tx) => { - await tx.contractTerms.put({ - h: contractTermsHash, - contractTermsRaw: contractTerms, - }), - await tx.peerPullDebit.add({ - peerPullDebitId, - contractPriv: contractPriv, - exchangeBaseUrl: exchangeBaseUrl, - pursePub: pursePub, - timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), - contractTermsHash, - amount: contractTerms.amount, - status: PeerPullDebitRecordStatus.DialogProposed, - totalCostEstimated: Amounts.stringify(totalAmount), - }); - }); + await wex.db.runReadWriteTx( + ["peerPullDebit", "contractTerms"], + async (tx) => { + await tx.contractTerms.put({ + h: contractTermsHash, + contractTermsRaw: contractTerms, + }), + await tx.peerPullDebit.add({ + peerPullDebitId, + contractPriv: contractPriv, + exchangeBaseUrl: exchangeBaseUrl, + pursePub: pursePub, + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), + contractTermsHash, + amount: contractTerms.amount, + status: PeerPullDebitRecordStatus.DialogProposed, + totalCostEstimated: Amounts.stringify(totalAmount), + }); + }, + ); return { amount: contractTerms.amount, diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index c6ece1536..9c272ad18 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -107,7 +107,7 @@ import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; const logger = new Logger("refresh.ts"); export class RefreshTransactionContext implements TransactionContext { - public transactionId: TransactionIdStr; + readonly transactionId: TransactionIdStr; readonly taskId: TaskIdStr; constructor( @@ -126,53 +126,54 @@ export class RefreshTransactionContext implements TransactionContext { async deleteTransaction(): Promise<void> { const refreshGroupId = this.refreshGroupId; - const ws = this.wex; - await ws.db.runReadWriteTx(["refreshGroups", "tombstones"], async (tx) => { - const rg = await tx.refreshGroups.get(refreshGroupId); - if (rg) { - await tx.refreshGroups.delete(refreshGroupId); - await tx.tombstones.put({ - id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId, - }); - } - }); + await this.wex.db.runReadWriteTx( + ["refreshGroups", "tombstones"], + async (tx) => { + const rg = await tx.refreshGroups.get(refreshGroupId); + if (rg) { + await tx.refreshGroups.delete(refreshGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId, + }); + } + }, + ); } async suspendTransaction(): Promise<void> { const { wex, refreshGroupId, transactionId } = this; - let res = await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => { - const dg = await tx.refreshGroups.get(refreshGroupId); - if (!dg) { - logger.warn( - `can't suspend refresh group, refreshGroupId=${refreshGroupId} not found`, - ); - return undefined; - } - const oldState = computeRefreshTransactionState(dg); - switch (dg.operationStatus) { - case RefreshOperationStatus.Finished: + let transitionInfo = await wex.db.runReadWriteTx( + ["refreshGroups"], + async (tx) => { + const dg = await tx.refreshGroups.get(refreshGroupId); + if (!dg) { + logger.warn( + `can't suspend refresh group, refreshGroupId=${refreshGroupId} not found`, + ); return undefined; - case RefreshOperationStatus.Pending: { - dg.operationStatus = RefreshOperationStatus.Suspended; - await tx.refreshGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeRefreshTransactionState(dg), - }; } - case RefreshOperationStatus.Suspended: - return undefined; - } - return undefined; - }); - if (res) { - wex.ws.notify({ - type: NotificationType.TransactionStateTransition, - transactionId, - oldTxState: res.oldTxState, - newTxState: res.newTxState, - }); - } + const oldState = computeRefreshTransactionState(dg); + switch (dg.operationStatus) { + case RefreshOperationStatus.Finished: + case RefreshOperationStatus.Suspended: + case RefreshOperationStatus.Failed: + return undefined; + case RefreshOperationStatus.Pending: { + dg.operationStatus = RefreshOperationStatus.Suspended; + await tx.refreshGroups.put(dg); + break; + } + default: + assertUnreachable(dg.operationStatus); + } + return { + oldTxState: oldState, + newTxState: computeRefreshTransactionState(dg), + }; + }, + ); + wex.taskScheduler.stopShepherdTask(this.taskId); + notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise<void> { |