[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant-backoffice] 02/11: Bank tests.
From: |
gnunet |
Subject: |
[taler-merchant-backoffice] 02/11: Bank tests. |
Date: |
Mon, 20 Dec 2021 17:45:00 +0100 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository merchant-backoffice.
commit de67d9daef0b8f8fc444f8ce6f6e3525c536e9f0
Author: ms <ms@taler.net>
AuthorDate: Sat Dec 18 20:42:50 2021 +0100
Bank tests.
Testing appearance and disappearance of elements related
to a Taler withdrawal.
---
packages/bank/package.json | 1 +
packages/bank/src/pages/home/index.tsx | 185 ++++++++++++++++++++++++++----
packages/bank/tests/__tests__/homepage.js | 67 ++++++++++-
3 files changed, 223 insertions(+), 30 deletions(-)
diff --git a/packages/bank/package.json b/packages/bank/package.json
index 2b3b446..3db98b1 100644
--- a/packages/bank/package.json
+++ b/packages/bank/package.json
@@ -46,6 +46,7 @@
"@storybook/preset-scss": "^1.0.3",
"@testing-library/preact": "^2.0.1",
"@testing-library/preact-hooks": "^1.1.0",
+ "@testing-library/jest-dom": "^5.16.1",
"@types/enzyme": "^3.10.10",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^5.3.0",
diff --git a/packages/bank/src/pages/home/index.tsx
b/packages/bank/src/pages/home/index.tsx
index 04f01cb..f7b0f51 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -32,8 +32,15 @@ interface CredentialsRequestType {
interface PageStateType {
isLoggedIn: boolean;
hasError: boolean;
+ withdrawalInProgress: boolean;
error?: string;
talerWithdrawUri?: string;
+ withdrawalOutcome?: string;
+ /**
+ * Not strictly a presentational element, could
+ * be moved in a future "withdrawal state" object.
+ */
+ withdrawalId?: string;
}
/**
@@ -87,6 +94,7 @@ function usePageState(
state: PageStateType = {
isLoggedIn: false,
hasError: false,
+ withdrawalInProgress: false,
}
): [PageStateType, StateUpdater<PageStateType>] {
return useState<PageStateType>(state);
@@ -109,8 +117,91 @@ function usePageState(
* particular API call and updates the state accordingly.
* Whether a new component should be returned too, depends
* on each case.
+ *
+ * FIXME: setting the Authorization headers and possing
+ * the body to a POST request should be factored out in
+ * a helper function.
*/
+
+/**
+ * This function confirms a withdrawal operation AFTER
+ * the wallet has given the exchange's payment details
+ * to the bank (via the Integration API). Such details
+ * can be given by scanning a QR code or by passing the
+ * raw taler://withdraw-URI to the CLI wallet.
+ *
+ * This function will set the confirmation status in the
+ * 'page state' and let the related components refresh.
+ */
+async function confirmWithdrawalCall(
+ backendState: BackendStateTypeOpt,
+ withdrawalId: string | undefined,
+ pageStateSetter: StateUpdater<PageStateType>
+) {
+ if (typeof backendState === "undefined") {
+ console.log("Page has a problem: no credentials found in the state.");
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: "No credentials found in the state"}))
+ return;
+ }
+ if (typeof withdrawalId === "undefined") {
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: "Withdrawal ID wasn't found in the state; cannot confirm it."}))
+ return;
+ }
+ try {
+ let headers = new Headers();
+ /**
+ * NOTE: tests show that when a same object is being
+ * POSTed, caching might prevent same requests from being
+ * made. Hence, trying to POST twice the same amount might
+ * get silently ignored.
+ *
+ * headers.append("cache-control", "no-store");
+ * headers.append("cache-control", "no-cache");
+ * headers.append("pragma", "no-cache");
+ * */
+ headers.append(
+ "Authorization",
+ `Basic ${Buffer.from(backendState.username + ":" +
backendState.password).toString("base64")}`
+ );
+ var res = await fetch(
+
`${backendState.url}accounts/${backendState.username}/withdrawals/confirm`, {
+ method: 'POST',
+ headers: headers
+ })
+ } catch (error) {
+ console.log("Could not POST withdrawal confirmation to the bank", error);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: `Could not confirm the withdrawal: ${error}`}))
+ return;
+ }
+ if (!res.ok) {
+ console.log(`Withdrawal confirmation gave response error (${res.status})`,
res.statusText);
+ pageStateSetter((prevState) => ({
+ ...prevState,
+ hasError: true,
+ error: `Withdrawal confirmation gave response error (${res.status})`}))
+ return;
+ } else {
+ console.log("Withdrawal operation confirmed!");
+ pageStateSetter((prevState) => {
+ delete prevState.talerWithdrawUri;
+ const { talerWithdrawUri, ...rest } = prevState;
+ return {
+ ...rest,
+ withdrawalOutcome: "Withdrawal confirmed!"
+ }})
+ }
+}
+
/**
* This function creates a withdrawal operation via the Access API.
*
@@ -121,7 +212,7 @@ function usePageState(
* After the scan, the page should refresh itself and inform the user
* about the operation's outcome.
*/
-async function createWithdrawalOperation(
+async function createWithdrawalCall(
amount: string,
backendState: BackendStateTypeOpt,
pageStateSetter: StateUpdater<PageStateType>
@@ -177,7 +268,9 @@ async function createWithdrawalOperation(
let resp = await res.json();
pageStateSetter((prevState) => ({
...prevState,
- talerWithdrawUri: resp.taler_withdraw_uri}))
+ withdrawalInProgress: true,
+ talerWithdrawUri: resp.taler_withdraw_uri,
+ withdrawalId: resp.withdrawal_id}))
}
}
@@ -268,7 +361,7 @@ async function registrationCall(
* Show only the account's balance.
*/
function Account(props: any) {
- const { talerWithdrawUri, accountLabel } = props;
+ const { withdrawalOutcome, talerWithdrawUri, accountLabel } = props;
const { data, error } = useSWR(`accounts/${props.accountLabel}`);
console.log("account data", data);
console.log("account error", error);
@@ -285,7 +378,14 @@ function Account(props: any) {
}
}
}
+
if (!data) return <p>Retrieving the profile page...</p>;
+ if (withdrawalOutcome) {
+ return <div>
+ <p>{withdrawalOutcome}</p>
+ {props.children}
+ </div>
+ }
/**
* A Taler withdrawal replaces everything in the page and
* starts polling the backend until either the wallet selected
@@ -296,12 +396,15 @@ function Account(props: any) {
* the outcome.
*/
if (talerWithdrawUri) {
- return <p>Give this address to your Taler wallet: {talerWithdrawUri}</p>
+ return (<div>
+ <p>Give this address to the Taler wallet: {talerWithdrawUri}</p>
+ {props.children}
+ </div>);
}
- return <div>
+ return (<div>
<p>Your balance is {data.balance.amount}.</p>
{props.children}
- </div>
+ </div>);
}
/**
@@ -327,10 +430,7 @@ function SWRWithCredentials(props: any): VNode {
return r.json()
}
),
- }}
- >
- {props.children}
- </SWRConfig>
+ }}>{props.children}</SWRConfig>
);
}
@@ -348,9 +448,10 @@ export function BankHome(): VNode {
}
/**
- * Credentials were correct, now try to render the
- * bank account page, with balance and transactions
- * history */
+ * Credentials were correct, now render the bank account page,
+ * with balance, transactions history, and a Taler withdrawal
+ * button.
+ */
if (pageState.isLoggedIn) {
if (typeof backendState === "undefined") {
pageStateSetter((prevState) => ({ ...prevState, hasError: true }));
@@ -360,18 +461,52 @@ export function BankHome(): VNode {
<SWRWithCredentials
username={backendState.username}
password={backendState.password}
- backendUrl={backendState.url}
- >
- <Account talerWithdrawUri={pageState.talerWithdrawUri}
- accountLabel={backendState.username}>
- <button
- onClick={() => {
- createWithdrawalOperation(
- "EUR:5",
- backendState,
- pageStateSetter
- )}}
- >Charge Taler wallet</button>
+ backendUrl={backendState.url}>
+ /**
+ * Account layer: GETs only (balance and transactions history).
+ */
+ <Account
+ withdrawalOutcome={pageState.withdrawalOutcome}
+ talerWithdrawUri={pageState.talerWithdrawUri}
+ accountLabel={backendState.username}>
+
+ /**
+ * Create Taler withdrawal operation via the Access API.
+ */
+ {!pageState.withdrawalInProgress && <button onClick={() => {
+ createWithdrawalCall(
+ "EUR:5",
+ backendState,
+ pageStateSetter
+ )}}>Charge Taler wallet</button>
+ }
+
+ /**
+ * This button turns visible only after a withdrawal reaches
+ * a persistent state (success or error), and lets the user
+ * navigate back to the main account / profile page.
+ */
+ {pageState.withdrawalOutcome && <button onClick={() => {
+ pageStateSetter((prevState) => {
+ const { withdrawalOutcome, ...rest } = prevState;
+ return {...rest, withdrawalInProgress:
false};})}}>Close</button>
+ }
+
+ /**
+ * This button turns visible after a withdrawal operation
+ * gets created and let the user confirm the operation after
+ * the wallet has scanned/parsed the withdraw URI.
+ *
+ * Eventually, it will be replaced by a background task that
+ * checks whether the wallet has POSTed the exchange's payment
+ * details.
+ */
+ {pageState.talerWithdrawUri && <button onClick={() => {
+ confirmWithdrawalCall(
+ backendState,
+ pageState.withdrawalId,
+ pageStateSetter);}}>Confirm withdrawal</button>
+ }
</Account>
</SWRWithCredentials>
);
diff --git a/packages/bank/tests/__tests__/homepage.js
b/packages/bank/tests/__tests__/homepage.js
index 46c6ae8..645c733 100644
--- a/packages/bank/tests/__tests__/homepage.js
+++ b/packages/bank/tests/__tests__/homepage.js
@@ -1,8 +1,9 @@
import "core-js/stable";
import "regenerator-runtime/runtime";
+import "@testing-library/jest-dom";
import { BankHome } from '../../src/pages/home';
import { h } from 'preact';
-import { cleanup, render, fireEvent, screen } from '@testing-library/preact';
+import { waitFor, cleanup, render, fireEvent, screen } from
'@testing-library/preact';
import expect from 'expect';
import fetchMock from "jest-fetch-mock";
@@ -52,8 +53,11 @@ describe("withdraw", () => {
paytoUri: "payto://iban/123/ABC"
}))
fireEvent.click(signupButton);
+ context.username = username;
})
+ let context = {username: null};
+
test("network failure before withdrawal creation", async () => {
let withdrawButton = screen.getByText("Charge Taler wallet");
// mock network failure.
@@ -64,17 +68,70 @@ describe("withdraw", () => {
test("HTTP response error upon withdrawal creation", async () => {
let withdrawButton = screen.getByText("Charge Taler wallet");
- // mock network failure.
fetch.once("{}", {status: 404});
fireEvent.click(withdrawButton);
await screen.findByText("gave response error", {exact: false})
})
- test("Successful withdraw", async () => {
+ test("Successful withdrawal creation and confirmation", async () => {
let withdrawButton = screen.getByText("Charge Taler wallet");
- fetch.once(JSON.stringify({taler_withdraw_uri: "taler://foo"}));
+ fetch.once(JSON.stringify({
+ taler_withdraw_uri: "taler://withdraw/foo",
+ withdrawal_id: "foo"
+ }));
+ /**
+ * After triggering a withdrawal, check if the taler://withdraw URI
+ * rendered, and confirm if so. Lastly, check that a success message
+ * appeared on the screen.
+ */
fireEvent.click(withdrawButton);
- await screen.findByText("give this address to your Taler wallet", {exact:
false})
+ expect(fetch).toHaveBeenCalledWith(
+ `http://localhost/accounts/${context.username}/withdrawals`,
+ expect.objectContaining({body: JSON.stringify({amount: "EUR:5"})})
+ )
+ await screen.findByText("give this address to", {exact: false})
+ // assume wallet POSTed the payment details.
+ const confirmButton = await screen.findByText("confirm withdrawal",
{exact: false})
+ await waitFor(() => expect(
+ screen.queryByText(
+ "charge taler wallet",
+ {exact: false})).not.toBeInTheDocument()
+ );
+ fetch.once("{}")
+ fireEvent.click(confirmButton);
+ /**
+ * After having confirmed above, wait that the
+ * pre-withdrawal elements disappears and a success
+ * message appears.
+ */
+ await waitFor(() => expect(
+ screen.queryByText(
+ "confirm withdrawal",
+ {exact: false})).not.toBeInTheDocument()
+ );
+ await waitFor(() => expect(
+ screen.queryByText(
+ "give this address to the taler wallet",
+ {exact: false})).not.toBeInTheDocument()
+ );
+ // success message
+ await screen.findByText("withdrawal confirmed", {exact: false})
+
+ /**
+ * Click on a "return to homepage" button, and check that
+ * the withdrawal confirmation is gone, and the option to
+ * withdraw again reappeared.
+ */
+ const closeButton = await screen.findByText("close", {exact: false})
+ fireEvent.click(closeButton);
+ await waitFor(() => expect(
+ screen.queryByText("withdrawal confirmed", {exact:
false})).not.toBeInTheDocument()
+ );
+ await waitFor(() => expect(
+ screen.queryByText(
+ "charge taler wallet",
+ {exact: false})).toBeInTheDocument()
+ );
})
})
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-merchant-backoffice] branch master updated (aa05541 -> 8003d41), gnunet, 2021/12/20
- [taler-merchant-backoffice] 03/11: bank tests, gnunet, 2021/12/20
- [taler-merchant-backoffice] 08/11: bank: fix URL, gnunet, 2021/12/20
- [taler-merchant-backoffice] 01/11: bank: implement login, gnunet, 2021/12/20
- [taler-merchant-backoffice] 04/11: bank: fix URL generation, gnunet, 2021/12/20
- [taler-merchant-backoffice] 10/11: bank: implement withdrawal abort, gnunet, 2021/12/20
- [taler-merchant-backoffice] 02/11: Bank tests.,
gnunet <=
- [taler-merchant-backoffice] 11/11: bank: test withdrawal abort, gnunet, 2021/12/20
- [taler-merchant-backoffice] 09/11: bank: fix URL, gnunet, 2021/12/20
- [taler-merchant-backoffice] 06/11: remove misplaced comments, gnunet, 2021/12/20
- [taler-merchant-backoffice] 05/11: bank: make tests 'Content-Type' aware, gnunet, 2021/12/20
- [taler-merchant-backoffice] 07/11: bank: fix URL generation, gnunet, 2021/12/20