gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated (aa05541 -> 8003d41)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (aa05541 -> 8003d41)
Date: Mon, 20 Dec 2021 17:44:58 +0100

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository merchant-backoffice.

    from aa05541  bank: create withdrawal
     new 6924c5d  bank: implement login
     new de67d9d  Bank tests.
     new 2981b6e  bank tests
     new 871b716  bank: fix URL generation
     new a090677  bank: make tests 'Content-Type' aware
     new e08d8ad  remove misplaced comments
     new 8a96a2c  bank: fix URL generation
     new 4db6de3  bank: fix URL
     new 726c0a0  bank: fix URL
     new ee6e662  bank: implement withdrawal abort
     new 8003d41  bank: test withdrawal abort

The 11 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/bank/package.json                |   1 +
 packages/bank/src/pages/home/index.tsx    | 394 +++++++++++++++++++++++++-----
 packages/bank/tests/__tests__/homepage.js | 178 ++++++++++++--
 3 files changed, 494 insertions(+), 79 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 9ed70ea..fc52f58 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -21,7 +21,7 @@ interface BackendStateType {
 /**
  * Request body of /register.
  */
-interface RegistrationRequestType {
+interface CredentialsRequestType {
   username: string;
   password: string;
 }
@@ -32,8 +32,15 @@ interface RegistrationRequestType {
 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;
 }
 
 /**
@@ -48,6 +55,22 @@ interface AccountStateType {
  * Helpers. *
  ***********/
 
+/**
+ * Craft headers with Authorization and Content-Type.
+ */
+function prepareHeaders(username: string, password: string) {
+  let headers = new Headers();
+  headers.append(
+    "Authorization",
+    `Basic ${Buffer.from(username + ":" + password).toString("base64")}`
+  );
+  headers.append(
+    "Content-Type",
+    "application/json"
+  )
+  return headers;
+}
+
 const getRootPath = () => {
   return typeof window !== undefined
     ? window.location.origin + window.location.pathname
@@ -87,6 +110,7 @@ function usePageState(
   state: PageStateType = {
     isLoggedIn: false,
     hasError: false,
+    withdrawalInProgress: false,
   }
 ): [PageStateType, StateUpdater<PageStateType>] {
   return useState<PageStateType>(state);
@@ -109,7 +133,154 @@ 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.
+ */
+
+/**
+ * Abort a withdrawal operation via the Access API's /abort.
+ */
+async function abortWithdrawalCall(
+  backendState: BackendStateTypeOpt,
+  withdrawalId: string | undefined,
+  pageStateSetter: StateUpdater<PageStateType>
+) {
+  if (typeof backendState === "undefined") {
+    console.log("No credentials found.");
+    pageStateSetter((prevState) => ({...prevState, hasError: true, error: "No 
credentials found."}))
+    return;
+  }
+  if (typeof withdrawalId === "undefined") {
+    console.log("No withdrawal ID found.");
+    pageStateSetter((prevState) => ({...prevState, hasError: true, error: "No 
withdrawal ID found."}))
+    return;
+  }
+
+  try {
+    const { username, password } = backendState;
+    let headers = prepareHeaders(username, password);
+    /**
+     * 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");
+     * */
+
+    // Backend URL must have been stored _with_ a final slash.
+    const url = new URL(
+      
`access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/abort`,
+       backendState.url
+    )
+    var res = await fetch(url.href, {method: 'POST', headers: headers})
+  } catch (error) {
+    console.log("Could not abort the withdrawal", error);
+    pageStateSetter((prevState) => ({
+      ...prevState,
+      hasError: true,
+      error: `Could not abort the withdrawal: ${error}`}))
+    return;
+  }
+  if (!res.ok) {
+    console.log(`Withdrawal abort gave response error (${res.status})`, 
res.statusText);
+    pageStateSetter((prevState) => ({
+      ...prevState,
+      hasError: true,
+      error: `Withdrawal abortion gave response error (${res.status})`}))
+      return;
+  } else {
+    console.log("Withdrawal operation aborted!");
+    pageStateSetter((prevState) => {
+      delete prevState.talerWithdrawUri;
+      const { talerWithdrawUri, ...rest } = prevState;
+      return {
+        ...rest,
+        withdrawalOutcome: "Withdrawal aborted!"
+      }})
+  }
+}
+
+/**
+ * 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("No credentials found.");
+    pageStateSetter((prevState) => ({...prevState, hasError: true, error: "No 
credentials found."}))
+    return;
+  }
+  if (typeof withdrawalId === "undefined") {
+    console.log("No withdrawal ID found.");
+    pageStateSetter((prevState) => ({...prevState, hasError: true, error: "No 
withdrawal ID found."}))
+    return;
+  }
+  
+  try {
+    const { username, password } = backendState;
+    let headers = prepareHeaders(username, password);
+    /**
+     * 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");
+     * */
+
+    // Backend URL must have been stored _with_ a final slash.
+    const url = new URL(
+      
`access-api/accounts/${backendState.username}/withdrawals/${withdrawalId}/confirm`,
+       backendState.url
+    )
+    var res = await fetch(url.href, {
+      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,21 +292,19 @@ 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>
 ) {
   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"}))
+    pageStateSetter((prevState) => ({...prevState, hasError: true, error: "No 
credentials given."}))
     return;
   }
   try {
-    let headers = new Headers();
+    const { username, password } = backendState;
+    let headers = prepareHeaders(username, password);
     /**
      * NOTE: tests show that when a same object is being
      * POSTed, caching might prevent same requests from being
@@ -146,12 +315,13 @@ async function createWithdrawalOperation(
      * 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`, {
+
+    // Backend URL must have been stored _with_ a final slash.
+    const url = new URL(
+      `access-api/accounts/${backendState.username}/withdrawals`,
+      backendState.url
+    )
+    var res = await fetch(url.href, {
         method: 'POST',
        headers: headers,
        body: JSON.stringify({amount: amount}),
@@ -177,10 +347,44 @@ 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}))
+  }
+}
+
+async function loginCall(
+  req: CredentialsRequestType,
+  /**
+   * FIXME: figure out if the two following
+   * functions can be retrieved somewhat from
+   * the state.
+   */
+  backendStateSetter: StateUpdater<BackendStateTypeOpt>,
+  pageStateSetter: StateUpdater<PageStateType>
+) {
+
+  /**
+   * Optimistically setting the state as 'logged in', and
+   * let the Account component request the balance to check
+   * whether the credentials are valid.  If not, then Account
+   * will switch the state back to 'logged out' (and the user
+   * will see again the login/register form).
+   */
+  pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true }));
+  let baseUrl = getRootPath();
+  if (!baseUrl.endsWith('/')) {
+    baseUrl += '/';
   }
+  backendStateSetter((prevState) => ({
+    ...prevState,
+    url: baseUrl,
+    username: req.username,
+    password: req.password,
+  }));
 }
 
+
 /**
  * This function requests /register.
  *
@@ -189,7 +393,7 @@ async function createWithdrawalOperation(
  * the page's (to indicate a successful login or a problem).
  */
 async function registrationCall(
-  req: RegistrationRequestType,
+  req: CredentialsRequestType,
   /**
    * FIXME: figure out if the two following
    * functions can be retrieved somewhat from
@@ -200,12 +404,25 @@ async function registrationCall(
 ) {
 
   let baseUrl = getRootPath();
-  let headersNoCache = new Headers();
+  /**
+   * If the base URL doesn't end with slash and the path
+   * is not empty, then the concatenation made by URL()
+   * drops the last path element.
+   */
+  if (!baseUrl.endsWith('/')) {
+    baseUrl += '/'
+  }
+  let headers = new Headers();
+  headers.append(
+    "Content-Type",
+    "application/json"
+  )
+  const url = new URL("access-api/testing/register", baseUrl)
   try {
-    var res = await fetch(
-      `${baseUrl}testing/register`, {
+    var res = await fetch(url.href, {
       method: 'POST',
       body: JSON.stringify(req),
+      headers: headers
     });
   } catch (error) {
     console.log("Could not POST new registration to the bank", error);
@@ -213,8 +430,13 @@ async function registrationCall(
     return;
   }
   if (!res.ok) {
+    const errorRaw = await res.text();
     console.log(`New registration gave response error (${res.status})`, 
res.statusText);
-    pageStateSetter((prevState) => ({ ...prevState, hasError: true }));
+    pageStateSetter((prevState) => ({
+      ...prevState,
+      hasError: true,
+      error: errorRaw
+    }));
   } else {
     console.log("Credentials are valid");
     pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true }));
@@ -235,12 +457,31 @@ async function registrationCall(
  * Show only the account's balance.
  */
 function Account(props: any) {
-  const { talerWithdrawUri, accountLabel } = props;
-  const { data, error } = useSWR(`accounts/${props.accountLabel}`);
-  if (typeof error != "undefined") {
-    return <p>Account information could not be retrieved</p>
+  const { withdrawalOutcome, talerWithdrawUri, accountLabel } = props;
+  const { data, error } = useSWR(`access-api/accounts/${props.accountLabel}`);
+  console.log("account data", data);
+  console.log("account error", error);
+  if (typeof error !== "undefined") {
+    switch(error.status) {
+      case 404: {
+        return <p>Username was not found</p>
+      }
+      case 401: {
+        return <p>Wrong credentials given</p>
+      }
+      default: {
+        return <p>Account information could not be retrieved.</p>
+      }
+    }
   }
+
   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
@@ -251,12 +492,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>);
 }
 
 /**
@@ -274,11 +518,15 @@ function SWRWithCredentials(props: any): VNode {
     <SWRConfig
       value={{
         fetcher: (url) =>
-          fetch(backendUrl + url || "", { headers: headers }).then((r) => 
r.json()),
-      }}
-    >
-      {props.children}
-    </SWRConfig>
+          fetch(backendUrl + url || "", { headers: headers }).then(
+           (r) => {
+             if (!r.ok) {
+               throw {status: r.status, json: r.json()};
+             }
+              return r.json()
+           }
+         ),
+      }}>{props.children}</SWRConfig>
   );
 }
 
@@ -296,9 +544,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 }));
@@ -308,18 +557,43 @@ 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
+         withdrawalOutcome={pageState.withdrawalOutcome}
+         talerWithdrawUri={pageState.talerWithdrawUri}
+         accountLabel={backendState.username}>
+
+         <button onClick={() => {
+            pageStateSetter({...pageState, isLoggedIn: false})
+         }}>Sign out</button>
+
+          {!pageState.withdrawalInProgress && <button onClick={() => {
+            createWithdrawalCall(
+              "EUR:5",
+              backendState,
+              pageStateSetter
+            )}}>Charge Taler wallet</button>
+         }
+
+         {pageState.withdrawalOutcome && <button onClick={() => {
+              pageStateSetter((prevState) => {
+               const { withdrawalOutcome, ...rest } = prevState;
+               return {...rest, withdrawalInProgress: 
false};})}}>Close</button>
+         }
+
+         {pageState.talerWithdrawUri && <div><button onClick={() => {
+            confirmWithdrawalCall(
+              backendState,
+             pageState.withdrawalId,
+             pageStateSetter);}}>Confirm withdrawal</button>
+            <button onClick={() => {
+              abortWithdrawalCall(
+                backendState,
+               pageState.withdrawalId,
+               pageStateSetter);}}>Abort withdrawal</button>
+         </div>}
+
        </Account>
       </SWRWithCredentials>
     );
@@ -337,17 +611,16 @@ export function BankHome(): VNode {
      */
   }
 
-  var registrationData: RegistrationRequestType;
+  var submitData: CredentialsRequestType;
   return (
     <div>
-      <p>Sign up!</p>
       <input
         type="text"
         placeholder="username"
         required
         onInput={(e): void => {
-          registrationData = {
-            ...registrationData,
+          submitData = {
+            ...submitData,
             username: e.currentTarget.value,
           };
         }}
@@ -357,8 +630,8 @@ export function BankHome(): VNode {
         placeholder="password"
         required
         onInput={(e): void => {
-          registrationData = {
-            ...registrationData,
+          submitData = {
+            ...submitData,
             password: e.currentTarget.value,
           };
         }}
@@ -367,13 +640,22 @@ export function BankHome(): VNode {
       <button
         onClick={() => {
           registrationCall(
-            registrationData,
+            submitData,
             backendStateSetter,
             pageStateSetter
           );
-        }}
-      >
-        Submit
+        }}>
+        Sign up
+      </button>
+      <button
+        onClick={() => {
+          loginCall(
+            submitData,
+            backendStateSetter,
+            pageStateSetter
+          );
+        }}>
+        Sign in 
       </button>
     </div>
   );
diff --git a/packages/bank/tests/__tests__/homepage.js 
b/packages/bank/tests/__tests__/homepage.js
index 03a909d..c3d370e 100644
--- a/packages/bank/tests/__tests__/homepage.js
+++ b/packages/bank/tests/__tests__/homepage.js
@@ -1,11 +1,22 @@
 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";
 
+
+beforeAll(() => {
+  Object.defineProperty(window, 'location', {
+    value: {
+      origin: "http://localhost";,
+      pathname: "/demobanks/default"
+    }
+  })
+})
+
 /**
  * Insert username and password into the registration
  * form and returns the submit button.  NOTE: the username
@@ -16,14 +27,19 @@ import fetchMock from "jest-fetch-mock";
  *
  * Return the username and the submit button.
  */
-function fillRegistrationForm() {
+function fillCredentialsForm() {
   const username = Math.random().toString().substring(2);
   const u = screen.getByPlaceholderText("username");
   const p = screen.getByPlaceholderText("password");
   fireEvent.input(u, {target: {value: username}})
   fireEvent.input(p, {target: {value: "bar"}})
-  const submitButton = screen.getByText("Submit");
-  return {username: username, submitButton: submitButton};
+  const signupButton = screen.getByText("Sign up");
+  const signinButton = screen.getByText("Sign in");
+  return {
+    username: username,
+    signupButton: signupButton,
+    signinButton: signinButton
+  };
 }
 fetchMock.enableMocks();
 
@@ -36,7 +52,7 @@ describe("withdraw", () => {
   // Register and land on the profile page.
   beforeEach(() => {
     render(<BankHome />);
-    const { username, submitButton } = fillRegistrationForm();
+    const { username, signupButton } = fillCredentialsForm();
     fetch.once("{}", {
       status: 200
     }).once(JSON.stringify({
@@ -46,9 +62,12 @@ describe("withdraw", () => {
       },
       paytoUri: "payto://iban/123/ABC"
     }))
-    fireEvent.click(submitButton);
+    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.
@@ -59,17 +78,92 @@ 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("Abort withdrawal", 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})
+    const abortButton = await screen.findByText("abort withdrawal", {exact: 
false})
+    fireEvent.click(abortButton);
+    expect(fetch).toHaveBeenLastCalledWith(
+    
`http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/abort`,
+    expect.anything()
+    )
+    await waitFor(() => expect(
+      screen.queryByText("abort withdrawal", {exact: 
false})).not.toBeInTheDocument());
+  })
+
+  test("Successful withdrawal creation and confirmation", async () => {
+    let withdrawButton = screen.getByText("Charge Taler wallet");
+    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);
+    expect(fetch).toHaveBeenCalledWith(
+      
`http://localhost/demobanks/default/access-api/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()
+    );
+    expect(fetch).toHaveBeenLastCalledWith(
+    
`http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/confirm`,
+    expect.anything())
+    // 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()
+    );
   })
 })
 
@@ -84,32 +178,66 @@ describe("home page", () => {
 
   test("new registration response error 404", async () => {
     render(<BankHome />);
-    let { username, submitButton } = fillRegistrationForm();
+    let { username, signupButton } = fillCredentialsForm();
     fetch.mockResponseOnce("Not found", {status: 404})
-    fireEvent.click(submitButton);
+    fireEvent.click(signupButton);
     await screen.findByText("has a problem", {exact: false});
     expect(fetch).toHaveBeenCalledWith(
-      "http://localhost/testing/register";,
-      {body: JSON.stringify({username: username, password: "bar"}), method: 
"POST"},
-    )
+      "http://localhost/demobanks/default/access-api/testing/register";,
+      expect.objectContaining(
+        {body: JSON.stringify({username: username, password: "bar"}), method: 
"POST"},
+    ))
   })
 
   test("registration network failure", async () => {
     render(<BankHome />);
-    const { username, submitButton } = fillRegistrationForm();
+    const { username, signupButton } = fillCredentialsForm();
     // Mocking network failure.
     fetch.mockReject("API is down");
-    fireEvent.click(submitButton);
+    fireEvent.click(signupButton);
     await screen.findByText("has a problem", {exact: false});
     expect(fetch).toHaveBeenCalledWith(
-      "http://localhost/testing/register";,
-      {body: JSON.stringify({username: username, password: "bar"}), method: 
"POST"},
-    )
+      "http://localhost/demobanks/default/access-api/testing/register";,
+      expect.objectContaining(
+        {body: JSON.stringify({username: username, password: "bar"}), method: 
"POST"}
+      ))
   })
   
+  test("login non existent user", async () => {
+    render(<BankHome />);
+    const { username, signinButton } = fillCredentialsForm();
+    fetch.once("{}", {status: 404});
+    fireEvent.click(signinButton);
+    await screen.findByText("username was not found", {exact: false})
+  })
+  test("login wrong credentials", async () => {
+    render(<BankHome />);
+    const { username, signinButton } = fillCredentialsForm();
+    fetch.once("{}", {status: 401});
+    fireEvent.click(signinButton);
+    await screen.findByText("wrong credentials given", {exact: false})
+  })
+  test("login success", async () => {
+    render(<BankHome />);
+    const { username, signinButton } = fillCredentialsForm();
+    fetch.once(JSON.stringify({
+      balance: {
+        amount: "EUR:10",
+       credit_debit_indicator: "credit"
+      },
+      paytoUri: "payto://iban/123/ABC"
+    }))
+    fireEvent.click(signinButton);
+    expect(fetch).toHaveBeenCalledWith(
+      `http://localhost/demobanks/default/access-api/accounts/${username}`,
+      expect.anything()
+    )
+    await screen.findByText("balance is EUR:10", {exact: false})
+  })
+
   test("registration success", async () => {
     render(<BankHome />);
-    const { username, submitButton } = fillRegistrationForm();
+    const { username, signupButton } = fillCredentialsForm();
     /**
      * Mock successful registration and balance request.
      */
@@ -122,7 +250,7 @@ describe("home page", () => {
       },
       paytoUri: "payto://iban/123/ABC"
     }))
-    fireEvent.click(submitButton);
+    fireEvent.click(signupButton);
     /**
      * Tests that a balance is shown after the successful
      * registration.
@@ -132,8 +260,12 @@ describe("home page", () => {
      * The expectation below tests whether the account
      * balance was requested after the successful registration.
      */
+    expect(fetch).toHaveBeenCalledWith(
+      "http://localhost/demobanks/default/access-api/testing/register";,
+      expect.anything() // no need to match auth headers.
+    )
     expect(fetch).toHaveBeenLastCalledWith(
-      `http://localhost/accounts/${username}`,
+      `http://localhost/demobanks/default/access-api/accounts/${username}`,
       expect.anything() // no need to match auth headers.
     )
   })

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]