diff options
author | Sebastian <sebasjm@gmail.com> | 2022-10-25 12:23:08 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-10-25 19:19:09 -0300 |
commit | a286649b0a611d87916a178a795c1acd2917741c (patch) | |
tree | 86d0a8eefb791ffebc877a88d6876d1ff764cf2b /packages/taler-wallet-webextension/src/cta | |
parent | 5fcd434f662bcef7e9e043763766c8b104ad6b97 (diff) | |
download | wallet-core-a286649b0a611d87916a178a795c1acd2917741c.tar.xz |
cherry-pick: using new wallet typed api missing test
Diffstat (limited to 'packages/taler-wallet-webextension/src/cta')
25 files changed, 876 insertions, 833 deletions
diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts index 0246b6f7c..539709821 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/index.ts @@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts index 5662a24c8..ba7bd147b 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/state.ts @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { Amounts, CreateDepositGroupResponse } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -29,10 +29,10 @@ export function useComponentState( if (!amountStr) throw Error("ERROR_NO-AMOUNT-FOR-DEPOSIT"); const amount = Amounts.parse(amountStr); if (!amount) throw Error("ERROR_INVALID-AMOUNT-FOR-DEPOSIT"); - const deposit = await api.prepareDeposit( - talerDepositUri, - Amounts.stringify(amount), - ); + const deposit = await api.wallet.call(WalletApiOperation.PrepareDeposit, { + amount: Amounts.stringify(amount), + depositPaytoUri: talerDepositUri, + }); return { deposit, uri: talerDepositUri, amount }; }); @@ -46,7 +46,10 @@ export function useComponentState( const { deposit, uri, amount } = info.response; async function doDeposit(): Promise<void> { - const resp = await api.createDepositGroup(uri, Amounts.stringify(amount)); + const resp = await api.wallet.call(WalletApiOperation.CreateDepositGroup, { + amount: Amounts.stringify(amount), + depositPaytoUri: uri, + }); onSuccess(resp.transactionId); } diff --git a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts index b6e63a796..f628b3287 100644 --- a/packages/taler-wallet-webextension/src/cta/Deposit/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Deposit/test.ts @@ -19,43 +19,40 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts, PrepareDepositResponse } from "@gnu-taler/taler-util"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Deposit CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerDepositUri: undefined, + amountStr: undefined, + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerDepositUri: undefined, - amountStr: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => ({}), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, - ), + useComponentState(props, mock), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); @@ -64,44 +61,41 @@ describe("Deposit CTA states", () => { if (error.operational) expect.fail(); expect(error.message).eq("ERROR_NO-URI-FOR-DEPOSIT"); } - await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready after loading", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + handler.addWalletCallResponse(WalletApiOperation.PrepareDeposit, undefined, { + effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), + totalDepositCost: Amounts.parseOrThrow("EUR:1.2"), + }); + const props = { + talerDepositUri: "payto://refund/asdasdas", + amountStr: "EUR:1", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerDepositUri: "payto://refund/asdasdas", - amountStr: "EUR:1", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareDeposit: async () => - ({ - effectiveDepositAmount: Amounts.parseOrThrow("EUR:1"), - totalDepositCost: Amounts.parseOrThrow("EUR:1.2"), - } as PrepareDepositResponse as any), - createDepositGroup: async () => ({}), - } as any, - ), + useComponentState(props, mock), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); @@ -112,5 +106,6 @@ describe("Deposit CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty"); }); }); diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts index ff04a8247..0389a17fb 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/index.ts @@ -22,7 +22,7 @@ import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts index 205a664e0..d845e121a 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts @@ -16,11 +16,11 @@ /* eslint-disable react-hooks/rules-of-hooks */ import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; type RecursiveState<S extends object> = S | (() => RecursiveState<S>); @@ -31,7 +31,7 @@ export function useComponentState( ): RecursiveState<State> { const amount = Amounts.parseOrThrow(amountStr); - const hook = useAsyncAsHook(api.listExchanges); + const hook = useAsyncAsHook(() => api.wallet.call(WalletApiOperation.ListExchanges, {})); if (!hook) { return { @@ -69,7 +69,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.initiatePeerPullPayment({ + const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPullPayment, { amount: Amounts.stringify(amount), exchangeBaseUrl: exchange.exchangeBaseUrl, partialContractTerms: { diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts index 7c6f13286..693803587 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/index.ts @@ -18,13 +18,13 @@ import { AbsoluteTime, AmountJson, PreparePayResult, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts index c88e80602..457827127 100644 --- a/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts +++ b/packages/taler-wallet-webextension/src/cta/InvoicePay/state.ts @@ -21,12 +21,12 @@ import { PreparePayResult, PreparePayResultType, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -34,18 +34,17 @@ export function useComponentState( api: typeof wxApi, ): State { const hook = useAsyncAsHook(async () => { - const p2p = await api.checkPeerPullPayment({ + const p2p = await api.wallet.call(WalletApiOperation.CheckPeerPullPayment, { talerUri: talerPayPullUri, }); - const balance = await api.getBalance(); + const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); return { p2p, balance }; }); - useEffect(() => { - api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { - hook?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.CoinWithdrawn], + hook?.retry + )); const [operationError, setOperationError] = useState< TalerErrorDetail | undefined @@ -64,10 +63,7 @@ export function useComponentState( }; } - // const { payStatus } = hook.response.p2p; - const { - amount: purseAmount, contractTerms, peerPullPaymentIncomingId, } = hook.response.p2p; @@ -136,17 +132,9 @@ export function useComponentState( }; } - // if (payStatus.status === PreparePayResultType.AlreadyConfirmed) { - // return { - // status: "confirmed", - // balance: foundAmount, - // ...baseResult, - // }; - // } - async function accept(): Promise<void> { try { - const resp = await api.acceptPeerPullPayment({ + const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPullPayment, { peerPullPaymentIncomingId, }); onSuccess(resp.transactionId); diff --git a/packages/taler-wallet-webextension/src/cta/Payment/index.ts b/packages/taler-wallet-webextension/src/cta/Payment/index.ts index 8e446722e..b4e59e666 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/index.ts @@ -15,21 +15,16 @@ */ import { - AmountJson, - ConfirmPayResult, - PreparePayResult, - PreparePayResultAlreadyConfirmed, - PreparePayResultInsufficientBalance, - PreparePayResultPaymentPossible, + AmountJson, PreparePayResult, + PreparePayResultAlreadyConfirmed, PreparePayResultPaymentPossible } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; -import { LoadingUriView, BaseView } from "./views.js"; +import { BaseView, LoadingUriView } from "./views.js"; export interface Props { talerPayUri?: string; diff --git a/packages/taler-wallet-webextension/src/cta/Payment/state.ts b/packages/taler-wallet-webextension/src/cta/Payment/state.ts index 8d388aa60..414bc2000 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/state.ts @@ -15,19 +15,16 @@ */ import { - AmountJson, - Amounts, - ConfirmPayResult, - ConfirmPayResultType, + Amounts, ConfirmPayResultType, NotificationType, PreparePayResultType, - TalerErrorCode, + TalerErrorCode } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -38,16 +35,17 @@ export function useComponentState( const hook = useAsyncAsHook(async () => { if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT"); - const payStatus = await api.preparePay(talerPayUri); - const balance = await api.getBalance(); + const payStatus = await api.wallet.call(WalletApiOperation.PreparePayForUri, { + talerPayUri: talerPayUri + }); + const balance = await api.wallet.call(WalletApiOperation.GetBalances, {}); return { payStatus, balance, uri: talerPayUri }; - }); + }, []); - useEffect(() => { - api.onUpdateNotification([NotificationType.CoinWithdrawn], () => { - hook?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.CoinWithdrawn], + hook?.retry + ), [hook]); const hookResponse = !hook || hook.hasError ? undefined : hook.response; @@ -127,7 +125,9 @@ export function useComponentState( hint: `payment is not possible: ${payStatus.status}`, }); } - const res = await api.confirmPay(payStatus.proposalId, undefined); + const res = await api.wallet.call(WalletApiOperation.ConfirmPay, { + proposalId: payStatus.proposalId, + }); // handle confirm pay if (res.type !== ConfirmPayResultType.Done) { throw TalerError.fromUncheckedDetail({ diff --git a/packages/taler-wallet-webextension/src/cta/Payment/test.ts b/packages/taler-wallet-webextension/src/cta/Payment/test.ts index f4ce5afb3..8aa099fdc 100644 --- a/packages/taler-wallet-webextension/src/cta/Payment/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Payment/test.ts @@ -20,81 +20,44 @@ */ import { - AmountJson, - Amounts, - BalancesResponse, - ConfirmPayResult, + Amounts, ConfirmPayResult, ConfirmPayResultType, - NotificationType, - PreparePayResult, - PreparePayResultType, + NotificationType, PreparePayResultInsufficientBalance, + PreparePayResultPaymentPossible, + PreparePayResultType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; -import { mountHook } from "../../test-utils.js"; +import { mountHook, nullFunction } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; -import * as wxApi from "../../wxApi.js"; - -const nullFunction: any = () => null; -type VoidFunction = () => void; - -type Subs = { - [key in NotificationType]?: VoidFunction; -}; - -export class SubsHandler { - private subs: Subs = {}; - - constructor() { - this.saveSubscription = this.saveSubscription.bind(this); - } - - saveSubscription( - messageTypes: NotificationType[], - callback: VoidFunction, - ): VoidFunction { - messageTypes.forEach((m) => { - this.subs[m] = callback; - }); - return nullFunction; - } - - notifyEvent(event: NotificationType): void { - const cb = this.subs[event]; - if (cb === undefined) - expect.fail(`Expected to have a subscription for ${event}`); - cb(); - } -} describe("Payment CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: undefined, + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: undefined, - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (error === undefined) expect.fail(); @@ -103,324 +66,312 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should response with no balance", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.InsufficientBalance, + amountRaw: "USD:10", + } as PreparePayResultInsufficientBalance) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { balances: [] }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - status: PreparePayResultType.InsufficientBalance, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "no-balance-for-currency") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "no-balance-for-currency") { + expect(r).eq({}) + return; + } expect(r.balance).undefined; expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should not be able to pay if there is no enough balance", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.InsufficientBalance, + amountRaw: "USD:10", + } as PreparePayResultInsufficientBalance) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:5", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - status: PreparePayResultType.InsufficientBalance, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:5", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "no-enough-balance") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:5")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to pay (without fee)", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:10", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => - useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:10", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, - ), + useComponentState(props, mock), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:10")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:0")); expect(r.payHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to pay (with fee)", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, + props, + mock + ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); expect(r.payHandler.onClick).not.undefined; } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should get confirmation done after pay successfully", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, { + type: ConfirmPayResultType.Done, + contractTerms: {}, + } as ConfirmPayResult) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - confirmPay: async () => - ({ - type: ConfirmPayResultType.Done, - contractTerms: {}, - } as Partial<ConfirmPayResult>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return; + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); if (r.payHandler.onClick === undefined) expect.fail(); r.payHandler.onClick(); } - // await waitNextUpdate(); - - // { - // const r = getLastResultOrThrow(); - // if (r.status !== "completed") expect.fail(); - // expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); - // expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); - // // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); - // // if (r.payResult.type !== ConfirmPayResultType.Done) expect.fail(); - // // expect(r.payResult.contractTerms).not.undefined; - // // expect(r.payHandler.onClick).undefined; - // } - await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should not stay in ready state after pay with error", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: nullFunction, + }; + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + handler.addWalletCallResponse(WalletApiOperation.ConfirmPay, undefined, { + type: ConfirmPayResultType.Pending, + lastError: { code: 1 }, + } as ConfirmPayResult) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: nullFunction, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: "USD:15", - }, - ], - } as Partial<BalancesResponse>), - confirmPay: async () => - ({ - type: ConfirmPayResultType.Pending, - lastError: { code: 1 }, - } as Partial<ConfirmPayResult>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); @@ -429,10 +380,10 @@ describe("Payment CTA states", () => { r.payHandler.onClick(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true { - const r = getLastResultOrThrow(); + const r = pullLastResultOrThrow(); if (r.status !== "ready") expect.fail(); expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); @@ -450,72 +401,91 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + + expect(handler.getCallingQueueState()).eq("empty") }); it("should update balance if a coins is withdraw", async () => { - const subscriptions = new SubsHandler(); - let availableBalance = Amounts.parseOrThrow("USD:10"); - - function notifyCoinWithdrawn(newAmount: AmountJson): void { - availableBalance = Amounts.add(availableBalance, newAmount).amount; - subscriptions.notifyEvent(NotificationType.CoinWithdrawn); + const { handler, mock } = createWalletApiMock(); + + const props = { + talerPayUri: "taller://pay", + cancel: nullFunction, + goToWalletManualWithdraw: nullFunction, + onSuccess: async () => { + null; + }, } - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:10", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + handler.addWalletCallResponse(WalletApiOperation.PreparePayForUri, undefined, { + status: PreparePayResultType.PaymentPossible, + amountRaw: "USD:9", + amountEffective: "USD:10", + } as PreparePayResultPaymentPossible) + + handler.addWalletCallResponse(WalletApiOperation.GetBalances, {}, { + balances: [{ + available: "USD:15", + hasPendingTransactions: false, + pendingIncoming: "USD:0", + pendingOutgoing: "USD:0", + requiresUserInput: false, + }] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerPayUri: "taller://pay", - cancel: nullFunction, - goToWalletManualWithdraw: nullFunction, - onSuccess: async () => { - null; - }, - }, - { - onUpdateNotification: subscriptions.saveSubscription, - preparePay: async () => - ({ - amountRaw: "USD:9", - amountEffective: "USD:10", - status: PreparePayResultType.PaymentPossible, - } as Partial<PreparePayResult>), - getBalance: async () => - ({ - balances: [ - { - available: Amounts.stringify(availableBalance), - }, - ], - } as Partial<BalancesResponse>), - } as Partial<typeof wxApi> as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:10")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); expect(r.payHandler.onClick).not.undefined; - notifyCoinWithdrawn(Amounts.parseOrThrow("USD:5")); + handler.notifyEventFromWallet(NotificationType.CoinWithdrawn); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const r = getLastResultOrThrow(); - if (r.status !== "ready") expect.fail(); + const r = pullLastResultOrThrow(); + if (r.status !== "ready") { + expect(r).eq({}) + return + } expect(r.balance).deep.equal(Amounts.parseOrThrow("USD:15")); expect(r.amount).deep.equal(Amounts.parseOrThrow("USD:9")); // expect(r.totalFees).deep.equal(Amounts.parseOrThrow("USD:1")); @@ -523,5 +493,6 @@ describe("Payment CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts index 013e9c041..4a65c571b 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/index.ts @@ -14,12 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { AmountJson } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts index 965a64e69..750fd22f7 100644 --- a/packages/taler-wallet-webextension/src/cta/Recovery/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Recovery/state.ts @@ -16,8 +16,7 @@ import { parseRecoveryUri } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import * as wxApi from "../../wxApi.js"; -import { wxClient } from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -49,7 +48,7 @@ export function useComponentState( const recovery = info; async function recoverBackup(): Promise<void> { - await wxClient.call(WalletApiOperation.ImportBackupRecovery, { recovery }); + await wxApi.wallet.call(WalletApiOperation.ImportBackupRecovery, { recovery }); onSuccess(); } diff --git a/packages/taler-wallet-webextension/src/cta/Refund/index.ts b/packages/taler-wallet-webextension/src/cta/Refund/index.ts index be9e3499b..6bd976aab 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/index.ts @@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { IgnoredView, InProgressView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/cta/Refund/state.ts b/packages/taler-wallet-webextension/src/cta/Refund/state.ts index 16dbbf70d..65a895fc3 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/state.ts @@ -15,9 +15,10 @@ */ import { Amounts, NotificationType } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useEffect, useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -28,15 +29,14 @@ export function useComponentState( const info = useAsyncAsHook(async () => { if (!talerRefundUri) throw Error("ERROR_NO-URI-FOR-REFUND"); - const refund = await api.prepareRefund({ talerRefundUri }); + const refund = await api.wallet.call(WalletApiOperation.PrepareRefund, { talerRefundUri }); return { refund, uri: talerRefundUri }; }); - useEffect(() => { - api.onUpdateNotification([NotificationType.RefreshMelted], () => { - info?.retry(); - }); - }); + useEffect(() => api.listener.onUpdateNotification( + [NotificationType.RefreshMelted], + info?.retry) + ); if (!info) { return { status: "loading", error: undefined }; @@ -51,7 +51,9 @@ export function useComponentState( const { refund, uri } = info.response; const doAccept = async (): Promise<void> => { - const res = await api.applyRefund(uri); + const res = await api.wallet.call(WalletApiOperation.ApplyRefund, { + talerRefundUri: uri + }); onSuccess(res.transactionId); }; diff --git a/packages/taler-wallet-webextension/src/cta/Refund/test.ts b/packages/taler-wallet-webextension/src/cta/Refund/test.ts index 3111a85c6..41996c133 100644 --- a/packages/taler-wallet-webextension/src/cta/Refund/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Refund/test.ts @@ -21,18 +21,19 @@ import { AmountJson, - Amounts, - NotificationType, - PrepareRefundResult, + Amounts, NotificationType, OrderShortInfo, PrepareRefundResult } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; -import { SubsHandler } from "../Payment/test.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Refund CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -44,24 +45,25 @@ describe("Refund CTA states", () => { null; }, }, - { - prepareRefund: async () => ({}), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + mock + // { + // prepareRefund: async () => ({}), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (!error) expect.fail(); @@ -71,55 +73,76 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready after loading", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - effectivePaid: "EUR:2", - awaiting: "EUR:2", - gone: "EUR:0", - granted: "EUR:0", - pending: false, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + props, mock + // { + // prepareRefund: async () => + // ({ + // effectivePaid: "EUR:2", + // awaiting: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:0", + // pending: false, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // }, + // } as PrepareRefundResult as any), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); @@ -131,58 +154,101 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ignored after clicking the ignore button", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + // handler.addWalletCall(WalletApiOperation.ApplyRefund) + // handler.addWalletCall(WalletApiOperation.PrepareRefund, undefined, { + // awaiting: "EUR:1", + // effectivePaid: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:1", + // pending: true, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // } as OrderShortInfo, + // }) + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - effectivePaid: "EUR:2", - awaiting: "EUR:2", - gone: "EUR:0", - granted: "EUR:0", - pending: false, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: async () => ({}), - } as any, + props, mock + // { + // prepareRefund: async () => + // ({ + // effectivePaid: "EUR:2", + // awaiting: "EUR:2", + // gone: "EUR:0", + // granted: "EUR:0", + // pending: false, + // proposalId: "1", + // info: { + // contractTermsHash: "123", + // merchant: { + // name: "the merchant name", + // }, + // orderId: "orderId1", + // summary: "the summary", + // }, + // } as PrepareRefundResult as any), + // applyRefund: async () => ({}), + // onUpdateNotification: async () => ({}), + // } as any, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); - - if (state.status !== "ready") expect.fail(); - if (state.error) expect.fail(); + const state = pullLastResultOrThrow(); + + if (state.status !== "ready") { + expect(state).eq({}) + return; + } + if (state.error) { + expect(state).eq({}) + return; + } expect(state.accept.onClick).not.undefined; expect(state.merchantName).eq("the merchant name"); expect(state.orderId).eq("orderId1"); @@ -192,113 +258,145 @@ describe("Refund CTA states", () => { state.ignore.onClick(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); - - if (state.status !== "ignored") expect.fail(); - if (state.error) expect.fail(); + const state = pullLastResultOrThrow(); + + if (state.status !== "ignored") { + expect(state).eq({}) + return; + } + if (state.error) { + expect(state).eq({}) + return; + } expect(state.merchantName).eq("the merchant name"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be in progress when doing refresh", async () => { - let granted = Amounts.getZero("EUR"); - const unit: AmountJson = { currency: "EUR", value: 1, fraction: 0 }; - const refunded: AmountJson = { currency: "EUR", value: 2, fraction: 0 }; - let awaiting: AmountJson = refunded; - let pending = true; - - const subscriptions = new SubsHandler(); - - function notifyMelt(): void { - granted = Amounts.add(granted, unit).amount; - pending = granted.value < refunded.value; - awaiting = Amounts.sub(refunded, granted).amount; - subscriptions.notifyEvent(NotificationType.RefreshMelted); + const { handler, mock } = createWalletApiMock(); + const props = { + talerRefundUri: "taler://refund/asdasdas", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, } - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:2", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:0", + pending: true, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:1", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:1", + pending: true, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + handler.addWalletCallResponse(WalletApiOperation.PrepareRefund, undefined, { + awaiting: "EUR:0", + effectivePaid: "EUR:2", + gone: "EUR:0", + granted: "EUR:2", + pending: false, + proposalId: "1", + info: { + contractTermsHash: "123", + merchant: { + name: "the merchant name", + }, + orderId: "orderId1", + summary: "the summary", + } as OrderShortInfo, + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( - { - talerRefundUri: "taler://refund/asdasdas", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - prepareRefund: async () => - ({ - awaiting: Amounts.stringify(awaiting), - effectivePaid: "EUR:2", - gone: "EUR:0", - granted: Amounts.stringify(granted), - pending, - proposalId: "1", - info: { - contractTermsHash: "123", - merchant: { - name: "the merchant name", - }, - orderId: "orderId1", - summary: "the summary", - }, - } as PrepareRefundResult as any), - applyRefund: async () => ({}), - onUpdateNotification: subscriptions.saveSubscription, - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "in-progress") expect.fail("1"); + if (state.status !== "in-progress") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); // expect(state.progress).closeTo(1 / 3, 0.01) - notifyMelt(); + handler.notifyEventFromWallet(NotificationType.RefreshMelted) } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "in-progress") expect.fail("2"); + if (state.status !== "in-progress") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:2")); // expect(state.progress).closeTo(2 / 3, 0.01) - notifyMelt(); + handler.notifyEventFromWallet(NotificationType.RefreshMelted) } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "ready") expect.fail("3"); + if (state.status !== "ready") { + expect(state).eq({}) + return; + } if (state.error) expect.fail(); expect(state.merchantName).eq("the merchant name"); expect(state.products).undefined; @@ -306,5 +404,6 @@ describe("Refund CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/Tip/index.ts b/packages/taler-wallet-webextension/src/cta/Tip/index.ts index 03cbd2196..520d854f2 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/index.ts @@ -19,13 +19,13 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { AcceptedView, IgnoredView, LoadingUriView, - ReadyView, + ReadyView } from "./views.js"; export interface Props { diff --git a/packages/taler-wallet-webextension/src/cta/Tip/state.ts b/packages/taler-wallet-webextension/src/cta/Tip/state.ts index f6721d504..00e1fddad 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/state.ts @@ -15,20 +15,18 @@ */ import { Amounts } from "@gnu-taler/taler-util"; -import { useState } from "preact/hooks"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( { talerTipUri, onCancel, onSuccess }: Props, api: typeof wxApi, ): State { - const [tipIgnored, setTipIgnored] = useState(false); - const tipInfo = useAsyncAsHook(async () => { if (!talerTipUri) throw Error("ERROR_NO-URI-FOR-TIP"); - const tip = await api.prepareTip({ talerTipUri }); + const tip = await api.wallet.call(WalletApiOperation.PrepareTip, { talerTipUri }); return { tip }; }); @@ -48,7 +46,7 @@ export function useComponentState( const { tip } = tipInfo.response; const doAccept = async (): Promise<void> => { - const res = await api.acceptTip({ walletTipId: tip.walletTipId }); + const res = await api.wallet.call(WalletApiOperation.AcceptTip, { walletTipId: tip.walletTipId }); //FIX: this may not be seen since we are moving to the success also tipInfo.retry(); @@ -65,13 +63,6 @@ export function useComponentState( }, }; - if (tipIgnored) { - return { - status: "ignored", - ...baseInfo, - }; - } - if (tip.accepted) { return { status: "accepted", diff --git a/packages/taler-wallet-webextension/src/cta/Tip/test.ts b/packages/taler-wallet-webextension/src/cta/Tip/test.ts index 47d9aa8db..69badbede 100644 --- a/packages/taler-wallet-webextension/src/cta/Tip/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Tip/test.ts @@ -19,14 +19,18 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { Amounts, PrepareTipResult } from "@gnu-taler/taler-util"; +import { Amounts } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentState } from "./state.js"; describe("Tip CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -38,23 +42,20 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => ({}), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading-uri"); if (!error) expect.fail(); @@ -64,12 +65,26 @@ describe("Tip CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ready for accepting the tip", async () => { - let tipAccepted = false; - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: false, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1 + }, + tipAmountRaw: "" + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -81,58 +96,79 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - accepted: tipAccepted, - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => { - tipAccepted = true; - }, - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "ready") expect.fail(); + if (state.status !== "ready") { + expect(state).eq({ status: "ready" }) + return; + } if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); if (state.accept.onClick === undefined) expect.fail(); + handler.addWalletCallResponse(WalletApiOperation.AcceptTip); state.accept.onClick(); } - await waitNextUpdate(); + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: true, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1 + }, + tipAmountRaw: "" + }); + expect(await waitForStateUpdate()).true; + { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); - if (state.status !== "accepted") expect.fail(); + if (state.status !== "accepted") { + expect(state).eq({ status: "accepted" }) + return; + } if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be ignored after clicking the ignore button", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + accepted: false, + expirationTimestamp: { + t_s: 1, + }, + tipAmountRaw: "" + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -144,52 +180,48 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "ready") expect.fail(); if (state.error) expect.fail(); expect(state.amount).deep.eq(Amounts.parseOrThrow("EUR:1")); expect(state.merchantBaseUrl).eq("merchant url"); expect(state.exchangeBaseUrl).eq("exchange url"); - // if (state.ignore.onClick === undefined) expect.fail(); - - // state.ignore.onClick(); } - // await waitNextUpdate(); - // { - // const state = getLastResultOrThrow(); - - // if (state.status !== "ignored") expect.fail(); - // if (state.error) expect.fail(); - // } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should render accepted if the tip has been used previously", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + + handler.addWalletCallResponse(WalletApiOperation.PrepareTip, undefined, { + accepted: true, + exchangeBaseUrl: "exchange url", + merchantBaseUrl: "merchant url", + tipAmountEffective: "EUR:1", + walletTipId: "tip_id", + expirationTimestamp: { + t_s: 1, + }, + tipAmountRaw: "", + }); + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentState( { @@ -201,30 +233,20 @@ describe("Tip CTA states", () => { null; }, }, - { - prepareTip: async () => - ({ - accepted: true, - exchangeBaseUrl: "exchange url", - merchantBaseUrl: "merchant url", - tipAmountEffective: "EUR:1", - walletTipId: "tip_id", - } as PrepareTipResult as any), - acceptTip: async () => ({}), - } as any, + mock, ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); if (state.status !== "accepted") expect.fail(); if (state.error) expect.fail(); @@ -233,5 +255,6 @@ describe("Tip CTA states", () => { expect(state.exchangeBaseUrl).eq("exchange url"); } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts index 820bffdea..83293438f 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/index.ts @@ -19,7 +19,7 @@ import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler, TextFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts index d4ba18c12..b229924b2 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferCreate/state.ts @@ -15,9 +15,9 @@ */ import { Amounts, TalerErrorDetail } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -33,7 +33,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.initiatePeerPushPayment({ + const resp = await api.wallet.call(WalletApiOperation.InitiatePeerPushPayment, { amount: Amounts.stringify(amount), partialContractTerms: { summary: subject, diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts index 0ffdb1b95..954243fe8 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/index.ts @@ -17,13 +17,13 @@ import { AbsoluteTime, AmountJson, - TalerErrorDetail, + TalerErrorDetail } from "@gnu-taler/taler-util"; import { Loading } from "../../components/Loading.js"; import { HookError } from "../../hooks/useAsyncAsHook.js"; import { ButtonHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentState } from "./state.js"; import { LoadingUriView, ReadyView } from "./views.js"; diff --git a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts index 0095910b5..4b860559e 100644 --- a/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts +++ b/packages/taler-wallet-webextension/src/cta/TransferPickup/state.ts @@ -18,12 +18,12 @@ import { AbsoluteTime, Amounts, TalerErrorDetail, - TalerProtocolTimestamp, + TalerProtocolTimestamp } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { Props, State } from "./index.js"; export function useComponentState( @@ -31,7 +31,7 @@ export function useComponentState( api: typeof wxApi, ): State { const hook = useAsyncAsHook(async () => { - return await api.checkPeerPushPayment({ + return await api.wallet.call(WalletApiOperation.CheckPeerPushPayment, { talerUri: talerPayPushUri, }); }, []); @@ -53,7 +53,6 @@ export function useComponentState( } const { - amount: purseAmount, contractTerms, peerPushPaymentIncomingId, } = hook.response; @@ -65,7 +64,7 @@ export function useComponentState( async function accept(): Promise<void> { try { - const resp = await api.acceptPeerPushPayment({ + const resp = await api.wallet.call(WalletApiOperation.AcceptPeerPushPayment, { peerPushPaymentIncomingId, }); onSuccess(resp.transactionId); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index a3b3df8b3..9a7acf9f1 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -20,15 +20,15 @@ import { HookError } from "../../hooks/useAsyncAsHook.js"; import { State as SelectExchangeState } from "../../hooks/useSelectedExchange.js"; import { ButtonHandler, SelectFieldHandler } from "../../mui/handlers.js"; import { compose, StateViewMap } from "../../utils/index.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { useComponentStateFromParams, - useComponentStateFromURI, + useComponentStateFromURI } from "./state.js"; import { ExchangeSelectionPage } from "../../wallet/ExchangeSelection/index.js"; -import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; import { NoExchangesView } from "../../wallet/ExchangeSelection/views.js"; +import { LoadingInfoView, LoadingUriView, SuccessView } from "./views.js"; export interface PropsFromURI { talerWithdrawUri: string | undefined; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index c542d8aae..704ef1ac3 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -19,13 +19,13 @@ import { AmountJson, Amounts, ExchangeListItem, - ExchangeTosStatus, + ExchangeTosStatus } from "@gnu-taler/taler-util"; -import { TalerError } from "@gnu-taler/taler-wallet-core"; +import { TalerError, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useState } from "preact/hooks"; import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js"; import { useSelectedExchange } from "../../hooks/useSelectedExchange.js"; -import * as wxApi from "../../wxApi.js"; +import { wxApi } from "../../wxApi.js"; import { PropsFromParams, PropsFromURI, State } from "./index.js"; type RecursiveState<S extends object> = S | (() => RecursiveState<S>); @@ -35,7 +35,7 @@ export function useComponentStateFromParams( api: typeof wxApi, ): RecursiveState<State> { const uriInfoHook = useAsyncAsHook(async () => { - const exchanges = await api.listExchanges(); + const exchanges = await api.wallet.call(WalletApiOperation.ListExchanges, {}); return { amount: Amounts.parseOrThrow(amount), exchanges }; }); @@ -58,11 +58,11 @@ export function useComponentStateFromParams( transactionId: string; confirmTransferUrl: string | undefined; }> { - const res = await api.acceptManualWithdrawal( - exchange, - Amounts.stringify(chosenAmount), - ageRestricted, - ); + const res = await api.wallet.call(WalletApiOperation.AcceptManualWithdrawal, { + exchangeBaseUrl: exchange, + amount: Amounts.stringify(chosenAmount), + restrictAge: ageRestricted, + }); return { confirmTransferUrl: undefined, transactionId: res.transactionId, @@ -93,16 +93,15 @@ export function useComponentStateFromURI( const uriInfoHook = useAsyncAsHook(async () => { if (!talerWithdrawUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL"); - const uriInfo = await api.getWithdrawalDetailsForUri({ + const uriInfo = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri, }); - const exchanges = await api.listExchanges(); const { amount, defaultExchangeBaseUrl } = uriInfo; return { talerWithdrawUri, amount: Amounts.parseOrThrow(amount), thisExchange: defaultExchangeBaseUrl, - exchanges, + exchanges: uriInfo.possibleExchanges, }; }); @@ -118,7 +117,7 @@ export function useComponentStateFromURI( const uri = uriInfoHook.response.talerWithdrawUri; const chosenAmount = uriInfoHook.response.amount; const defaultExchange = uriInfoHook.response.thisExchange; - const exchangeList = uriInfoHook.response.exchanges.exchanges; + const exchangeList = uriInfoHook.response.exchanges; async function doManagedWithdraw( exchange: string, @@ -127,7 +126,11 @@ export function useComponentStateFromURI( transactionId: string; confirmTransferUrl: string | undefined; }> { - const res = await api.acceptWithdrawal(uri, exchange, ageRestricted); + const res = await api.wallet.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { + exchangeBaseUrl: exchange, + talerWithdrawUri: uri, + restrictAge: ageRestricted + }); return { confirmTransferUrl: res.confirmTransferUrl, transactionId: res.transactionId, @@ -186,7 +189,7 @@ function exchangeSelectionState( * about the withdrawal */ const amountHook = useAsyncAsHook(async () => { - const info = await api.getWithdrawalDetailsForAmount({ + const info = await api.wallet.call(WalletApiOperation.GetWithdrawalDetailsForAmount, { exchangeBaseUrl: currentExchange.exchangeBaseUrl, amount: Amounts.stringify(chosenAmount), restrictAge: ageRestricted, @@ -261,10 +264,10 @@ function exchangeSelectionState( //TODO: calculate based on exchange info const ageRestriction = ageRestrictionEnabled ? { - list: ageRestrictionOptions, - value: String(ageRestricted), - onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), - } + list: ageRestrictionOptions, + value: String(ageRestricted), + onChange: async (v: string) => setAgeRestricted(parseInt(v, 10)), + } : undefined; return { diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 977faa03a..b4ba32f8a 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -21,16 +21,12 @@ import { Amounts, - ExchangeEntryStatus, - ExchangeFullDetails, - ExchangeListItem, - ExchangesListResponse, - ExchangeTosStatus, - GetExchangeTosResult, - ManualWithdrawalDetails, + ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { expect } from "chai"; import { mountHook } from "../../test-utils.js"; +import { createWalletApiMock } from "../../test-utils.js"; import { useComponentStateFromURI } from "./state.js"; const exchanges: ExchangeListItem[] = [ @@ -65,39 +61,32 @@ const exchanges: ExchangeListItem[] = [ describe("Withdraw CTA states", () => { it("should tell the user that the URI is missing", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: undefined, + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: undefined, - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForAmount: async ({ - talerWithdrawUri, - }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - }), - } as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); if (status != "uri-error") expect.fail(); if (!error) expect.fail(); @@ -107,40 +96,41 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should tell the user that there is not known exchange", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "EUR:2", + possibleExchanges: [], + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "EUR:2", - possibleExchanges: [], - }), - } as any, + props, mock ), ); { - const { status } = getLastResultOrThrow(); + const { status } = pullLastResultOrThrow(); expect(status).equals("loading", "1"); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("no-exchange", "3"); @@ -148,65 +138,60 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); it("should be able to withdraw if tos are ok", async () => { - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, + } + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl + }) + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, { + amountRaw: "ARS:2", + amountEffective: "ARS:2", + paytoUris: ["payto://"], + tosAccepted: true, + ageRestrictionOptions: [] + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => ({ exchanges }), - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, - }), - getWithdrawalDetailsForAmount: - async (): Promise<ManualWithdrawalDetails> => - ({ - amountRaw: "ARS:2", - amountEffective: "ARS:2", - } as any), - getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ - contentType: "text", - content: "just accept", - acceptedEtag: "v1", - currentEtag: "v1", - tosStatus: ExchangeTosStatus.Accepted, - }), - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -218,82 +203,72 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); - it("should be accept the tos before withdraw", async () => { - const listExchangesResponse: ExchangesListResponse = { - exchanges: exchanges.map((e) => ({ - ...e, - tosStatus: ExchangeTosStatus.New, - })), - }; - - function updateAcceptedVersionToCurrentVersion(): void { - listExchangesResponse.exchanges = listExchangesResponse.exchanges.map( - (e) => ({ - ...e, - tosStatus: ExchangeTosStatus.Accepted, - }), - ); + it("should accept the tos before withdraw", async () => { + const { handler, mock } = createWalletApiMock(); + const props = { + talerWithdrawUri: "taler-withdraw://", + cancel: async () => { + null; + }, + onSuccess: async () => { + null; + }, } - - const { getLastResultOrThrow, waitNextUpdate, assertNoPendingUpdate } = + const exchangeWithNewTos = exchanges.map((e) => ({ + ...e, + tosStatus: ExchangeTosStatus.New, + })); + + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchangeWithNewTos, + defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl + }) + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForAmount, undefined, { + amountRaw: "ARS:2", + amountEffective: "ARS:2", + paytoUris: ["payto://"], + tosAccepted: false, + ageRestrictionOptions: [] + }) + + + handler.addWalletCallResponse(WalletApiOperation.GetWithdrawalDetailsForUri, undefined, { + amount: "ARS:2", + possibleExchanges: exchanges, + defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl + }) + + const { pullLastResultOrThrow, waitForStateUpdate, assertNoPendingUpdate } = mountHook(() => useComponentStateFromURI( - { - talerWithdrawUri: "taler-withdraw://", - cancel: async () => { - null; - }, - onSuccess: async () => { - null; - }, - }, - { - listExchanges: async () => listExchangesResponse, - getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({ - amount: "ARS:2", - possibleExchanges: exchanges, - defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, - }), - getWithdrawalDetailsForAmount: - async (): Promise<ManualWithdrawalDetails> => - ({ - amountRaw: "ARS:2", - amountEffective: "ARS:2", - } as any), - getExchangeTos: async (): Promise<GetExchangeTosResult> => ({ - contentType: "text", - content: "just accept", - acceptedEtag: "v1", - currentEtag: "v2", - tosStatus: ExchangeTosStatus.Changed, - }), - setExchangeTosAccepted: async () => ({}), - } as any, + props, mock ), ); { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const { status, error } = getLastResultOrThrow(); + const { status, error } = pullLastResultOrThrow(); expect(status).equals("loading"); expect(error).undefined; } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -303,14 +278,14 @@ describe("Withdraw CTA states", () => { expect(state.doWithdrawal.onClick).undefined; - updateAcceptedVersionToCurrentVersion(); + // updateAcceptedVersionToCurrentVersion(); state.onTosUpdate(); } - await waitNextUpdate(); + expect(await waitForStateUpdate()).true; { - const state = getLastResultOrThrow(); + const state = pullLastResultOrThrow(); expect(state.status).equals("success"); if (state.status !== "success") return; @@ -322,5 +297,6 @@ describe("Withdraw CTA states", () => { } await assertNoPendingUpdate(); + expect(handler.getCallingQueueState()).eq("empty") }); }); |