merchant-backoffice

ZZZ: Inactive/Deprecated
Log | Files | Refs | Submodules | README

commit 838ec0ff5e7331fa4c969837189702bbe6d8d3e4
parent 56e0c024fab54143d206bc2097925caa2a1db3f2
Author: MS <ms@taler.net>
Date:   Sat, 19 Feb 2022 10:53:32 +0100

Adapt tests to new changes.

Diffstat:
Mpackages/bank/src/pages/home/index.tsx | 60+++++++++++++++++++++++++-----------------------------------
Mpackages/bank/tests/__tests__/homepage.js | 152+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
2 files changed, 115 insertions(+), 97 deletions(-)

diff --git a/packages/bank/src/pages/home/index.tsx b/packages/bank/src/pages/home/index.tsx @@ -509,18 +509,8 @@ async function createWithdrawalCall( 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. + // Let bank generate withdraw URI: const url = new URL( `access-api/accounts/${backendState.username}/withdrawals`, backendState.url @@ -562,8 +552,7 @@ async function loginCall( req: CredentialsRequestType, /** * FIXME: figure out if the two following - * functions can be retrieved somewhat from - * the state. + * functions can be retrieved from the state. */ backendStateSetter: StateUpdater<BackendStateTypeOpt>, pageStateSetter: StateUpdater<PageStateType> @@ -572,10 +561,7 @@ async function loginCall( /** * 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). - */ + * whether the credentials are valid. */ pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true })); let baseUrl = getRootPath(); if (!baseUrl.endsWith('/')) { @@ -635,7 +621,7 @@ async function registrationCall( return; } if (!res.ok) { - const errorRaw = JSON.stringify(await res.json()); + const errorRaw = await res.text(); console.log(`New registration gave response error (${res.status})`, errorRaw); pageStateSetter((prevState) => ({ ...prevState, @@ -717,7 +703,7 @@ function TalerWithdrawal(Props: any): VNode { submitAmount = submitAmount.replace(",", "."); // tolerating comma instead of point. const re = RegExp(amountRegex) if (!re.test(submitAmount)) { - console.log("Not withdrawing invalid amount", submitAmount); + console.log(`Not withdrawing invalid amount '${submitAmount}'.`); return; } console.log("Valid amount", submitAmount); @@ -842,9 +828,11 @@ function Transactions(Props: any): VNode { } } } - if (!data) return <p>"Transactions page loading..."</p>; - - console.log("History data", data); + if (!data) { + console.log(`History data of ${accountLabel} not arrived`); + return <p>"Transactions page loading..."</p>; + } + console.log(`History data of ${accountLabel}`, data); return <ul>{ data.transactions.map(function(item: any) { const sign = item.direction == "DBIT" ? "-" : ""; @@ -934,6 +922,7 @@ function Account(Props: any): VNode { * the outcome. */ if (talerWithdrawUri) { + console.log(`Showing withdraw URI: ${talerWithdrawUri}`); return (<Fragment> <p>Scan the following QR code, and then confirm!</p> <div>{QR({text: talerWithdrawUri})}</div> @@ -1006,6 +995,7 @@ function SWRWithoutCredentials(Props: any): VNode { * Show histories of public accounts. */ function PublicHistories(Props: any): VNode { + const [showAccount, setShowAccount] = useState<string | undefined>(); const { data, error } = useSWR("access-api/public-accounts") if (typeof error !== "undefined") { console.log("account error", error); @@ -1019,14 +1009,14 @@ function PublicHistories(Props: any): VNode { } } if (!data) return <p>Waiting public accounts list...</p> - var txs = {}; + var txs: any = {}; var accountsBar = []; + + // Ask first story of all the public accounts. for (const account of data.publicAccounts) { console.log("Asking transactions for", account.accountLabel) accountsBar.push( - <li> - <a onClick={() => <PublicAccounts showAccount={account.accountLabel} />}>{account.accountLabel}</a> - </li> + <li><a onClick={() => setShowAccount(account.accountLabel)}>{account.accountLabel}</a></li> ); txs[account.accountLabel] = <div>{account.accountLabel} latest transactions: @@ -1037,14 +1027,13 @@ function PublicHistories(Props: any): VNode { * Show the account specified in the props, or just one * from the list if that's not given. */ - var showAccount = Props.showAccount - if (typeof showAccount === "undefined" && keys(txs).length > 0) { - showAccount = keys(txs).pop() - } - + if (typeof showAccount === "undefined" && Object.keys(txs).length > 0) + setShowAccount(Object.keys(txs).pop()); + console.log(`Public history tab: ${showAccount}`); return <Fragment> <ul>{accountsBar}</ul> {typeof showAccount !== "undefined" ? txs[showAccount] : <p>No public transactions found.</p>} + {Props.children} </Fragment>; } @@ -1070,10 +1059,11 @@ export function BankHome(): VNode { if (pageState.showPublicHistories) { return (<SWRWithoutCredentials baseUrl={getRootPath()}> - <PublicHistories /> - <a onClick={() => { - pageStateSetter((prevState: PageStateType) => - ({...prevState, showPublicHistories: false}))}}>Go back</a> + <PublicHistories> + <a onClick={() => { + pageStateSetter((prevState: PageStateType) => + ({...prevState, showPublicHistories: false}))}}>Go back</a> + </PublicHistories> </SWRWithoutCredentials>); } diff --git a/packages/bank/tests/__tests__/homepage.js b/packages/bank/tests/__tests__/homepage.js @@ -26,35 +26,21 @@ beforeAll(() => { global.Storage.prototype.setItem = jest.fn((key, value) => {}) }) -/** - * Insert username and password into the registration - * form and returns the submit button. NOTE: the username - * must be given always fresh, as it acts as a SWR key and - * therefore might prevent calls from being made, because of - * caching reasons. That is not a problem per-se but can - * disrupt ".toHaveLastBeenCalledWith()"-like asserts. - * - * Return the username and the submit button. - */ 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 signupButton = screen.getByText("Sign up"); - const signinButton = screen.getByText("Sign in"); + const signinButton = screen.getByText("Login"); return { username: username, - signupButton: signupButton, signinButton: signinButton }; } fetchMock.enableMocks(); -function signUp(context) { - render(<BankHome />); - const { username, signupButton } = fillCredentialsForm(); +function mockSuccessLoginOrRegistration() { fetch.once("{}", { status: 200 }).once(JSON.stringify({ @@ -64,13 +50,31 @@ function signUp(context) { }, paytoUri: "payto://iban/123/ABC" })) - fireEvent.click(signupButton); +} + +/** + * Render homepage -> navigate to register page -> submit registration. + * 'webMock' is called before submission to mock the server response + */ +function signUp(context, webMock) { + render(<BankHome />); + const registerPage = screen.getByText("Register!"); + fireEvent.click(registerPage); + 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 registerButton = screen.getByText("Register"); + webMock(); + fireEvent.click(registerButton); context.username = username; + return context; } describe("wire transfer", () => { beforeEach(() => { - signUp({}); // context unused + signUp({}, mockSuccessLoginOrRegistration); // context unused }) test("Wire transfer success", async () => { const transferButton = screen.getByText("Create wire transfer"); @@ -98,14 +102,16 @@ describe("withdraw", () => { cleanup(); }) + + let context = {}; // Register and land on the profile page. beforeEach(() => { - signUp(context); + context = signUp(context, mockSuccessLoginOrRegistration); }) - let context = {username: null}; - test("network failure before withdrawal creation", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10"}}); let withdrawButton = screen.getByText("Charge Taler wallet"); // mock network failure. fetch.mockReject("API is down"); @@ -114,6 +120,8 @@ describe("withdraw", () => { }) test("HTTP response error upon withdrawal creation", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); let withdrawButton = screen.getByText("Charge Taler wallet"); fetch.once("{}", {status: 404}); fireEvent.click(withdrawButton); @@ -121,6 +129,8 @@ describe("withdraw", () => { }) test("Abort withdrawal", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); let withdrawButton = screen.getByText("Charge Taler wallet"); fetch.once(JSON.stringify({ taler_withdraw_uri: "taler://withdraw/foo", @@ -143,7 +153,9 @@ describe("withdraw", () => { }) test("Successful withdrawal creation and confirmation", async () => { - let withdrawButton = screen.getByText("Charge Taler wallet"); + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); + let withdrawButton = await screen.findByText("Charge Taler wallet"); fetch.once(JSON.stringify({ taler_withdraw_uri: "taler://withdraw/foo", withdrawal_id: "foo" @@ -151,12 +163,11 @@ describe("withdraw", () => { /** * 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. - */ + * 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"})}) + expect.objectContaining({body: JSON.stringify({amount: "EUR:10.0"})}) ) // assume wallet POSTed the payment details. const confirmButton = await screen.findByText("confirm withdrawal", {exact: false}) @@ -221,7 +232,11 @@ describe("home page", () => { cleanup(); }) test("public histories", async () => { - // Mock list of public accounts. + render(<BankHome />); + /** + * Mock list of public accounts. 'bar' is + * the shown account, since it occupies the last + * position (and SPA picks it via the 'pop()' method) */ fetch.once(JSON.stringify({ "publicAccounts" : [ { "balance" : "EUR:1", @@ -281,53 +296,80 @@ describe("home page", () => { date: "2000-01-01" }] })) - render(<BankHome />); + + // Navigate to dedicate public histories page. + const publicTxsPage = screen.getByText("transactions"); + fireEvent.click(publicTxsPage); + /** - * Check that transacions data appears on the page. + * Check that transactions data appears on the page. */ await screen.findByText("reimbursement", {exact: false}); - await screen.findByText("refund", {exact: false}); await screen.findByText("bonus", {exact: false}); - await screen.findByText("donation", {exact: false}); - + /** + * The transactions below should not appear, because only + * one public account renders. + */ + await waitFor(() => expect( + screen.queryByText("refund", {exact: false})).not.toBeInTheDocument()); + await waitFor(() => expect( + screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); + /** + * First HTTP mock: + */ await expect(fetch).toHaveBeenCalledWith( "http://localhost/demobanks/default/access-api/public-accounts" ) + /** + * Only expecting this request (second mock), as SWR doesn't let + * the unshown history request to the backend: + */ await expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/accounts/foo/transactions?page=0" + "http://localhost/demobanks/default/access-api/accounts/bar/transactions?page=0" ) + /** + * Switch tab: + */ + let fooTab = await screen.findByText("foo", {exact: false}); + fireEvent.click(fooTab); + /** + * Last two HTTP mocks should render now: + */ + await screen.findByText("refund", {exact: false}); + await screen.findByText("donation", {exact: false}); + + // Expect SWR to have requested 'foo' history + // (consuming the last HTTP mock): await expect(fetch).toHaveBeenCalledWith( - "http://localhost/demobanks/default/access-api/accounts/bar/transactions?page=0" + "http://localhost/demobanks/default/access-api/accounts/foo/transactions?page=0" ) + let backButton = await screen.findByText("Go back", {exact: false}); + fireEvent.click(backButton); + await waitFor(() => expect( + screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); + await screen.findByText("welcome to eufin bank", {exact: false}) }) // check page informs about the current balance // after a successful registration. test("new registration response error 404", async () => { - render(<BankHome />); - let { username, signupButton } = fillCredentialsForm(); - fetch.mockResponseOnce("Not found", {status: 404}) - fireEvent.click(signupButton); + var context = signUp({}, () => fetch.mockResponseOnce("Not found", {status: 404})); await screen.findByText("has a problem", {exact: false}); expect(fetch).toHaveBeenCalledWith( "http://localhost/demobanks/default/access-api/testing/register", expect.objectContaining( - {body: JSON.stringify({username: username, password: "bar"}), method: "POST"}, + {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"}, )) }) test("registration network failure", async () => { - render(<BankHome />); - const { username, signupButton } = fillCredentialsForm(); - // Mocking network failure. - fetch.mockReject("API is down"); - fireEvent.click(signupButton); + let context = signUp({}, ()=>fetch.mockReject("API is down")); await screen.findByText("has a problem", {exact: false}); expect(fetch).toHaveBeenCalledWith( "http://localhost/demobanks/default/access-api/testing/register", expect.objectContaining( - {body: JSON.stringify({username: username, password: "bar"}), method: "POST"} + {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"} )) }) @@ -391,7 +433,7 @@ describe("home page", () => { `http://localhost/demobanks/default/access-api/accounts/${username}`, expect.anything() ) - await screen.findByText("balance is EUR:10", {exact: false}) + await screen.findByText("balance is 10 EUR", {exact: false}) // The two transactions in the history mocked above. await screen.findByText("refund", {exact: false}) await screen.findByText("donation", {exact: false}) @@ -402,26 +444,12 @@ describe("home page", () => { }) test("registration success", async () => { - render(<BankHome />); - const { username, signupButton } = fillCredentialsForm(); - /** - * Mock successful registration and balance request. - */ - fetch.once("{}", { - status: 200 - }).once(JSON.stringify({ - balance: { - amount: "EUR:10", - credit_debit_indicator: "credit" - }, - paytoUri: "payto://iban/123/ABC" - })) - fireEvent.click(signupButton); + let context = signUp({}, mockSuccessLoginOrRegistration); /** * Tests that a balance is shown after the successful * registration. */ - await screen.findByText("balance is EUR:10", {exact: false}) + await screen.findByText("balance is 10 EUR", {exact: false}) /** * The expectation below tests whether the account * balance was requested after the successful registration. @@ -431,7 +459,7 @@ describe("home page", () => { expect.anything() // no need to match auth headers. ) expect(fetch).toHaveBeenCalledWith( - `http://localhost/demobanks/default/access-api/accounts/${username}`, + `http://localhost/demobanks/default/access-api/accounts/${context.username}`, expect.anything() // no need to match auth headers. ) })