From bd15cd41b92243b9d0bd252fb62bdf1a63c30f8a Mon Sep 17 00:00:00 2001 From: Siddharth Date: Wed, 17 Jan 2024 13:37:15 +0530 Subject: [PATCH] test(dashboard): added browser tests for batch payments (#3831) * chore:added batch payments * chore: misc changes in test * chore: update deps * chore: misc changes * chore: remove logs * chore: update deps * chore: update deps and added waiting time' * chore: adding more deps * chore: exit if user is not funded * chore: adding response log * chore: adding load wallet * chore: linux EOL --- .../components/batch-payments/index.tsx | 8 +- apps/dashboard/components/upload-button.tsx | 3 +- .../e2e/batch-payments/batch-payments.cy.ts | 61 +++++++++++ apps/dashboard/cypress/e2e/e2e.cy.ts | 1 + apps/dashboard/cypress/fixtures/template.csv | 5 + apps/dashboard/cypress/support/commands.ts | 102 +++++++++++------- apps/dashboard/cypress/support/test-data.ts | 33 ++++++ dev/BUCK | 10 ++ dev/Tiltfile | 32 ++++++ dev/bin/add-test-users-with-usernames.sh | 4 +- dev/bin/fund-user.sh | 16 +++ dev/helpers/gql/on-chain-address-create.gql | 8 ++ dev/helpers/gql/transactions.gql | 66 ++++++++++++ dev/helpers/gql/wallets-for-account.gql | 13 +++ dev/helpers/onchain.sh | 59 ++++++++++ 15 files changed, 379 insertions(+), 42 deletions(-) create mode 100644 apps/dashboard/cypress/e2e/batch-payments/batch-payments.cy.ts create mode 100644 apps/dashboard/cypress/fixtures/template.csv create mode 100755 dev/bin/fund-user.sh create mode 100644 dev/helpers/gql/on-chain-address-create.gql create mode 100644 dev/helpers/gql/transactions.gql create mode 100644 dev/helpers/gql/wallets-for-account.gql create mode 100644 dev/helpers/onchain.sh diff --git a/apps/dashboard/components/batch-payments/index.tsx b/apps/dashboard/components/batch-payments/index.tsx index 0ba1a010ab..01d323335b 100644 --- a/apps/dashboard/components/batch-payments/index.tsx +++ b/apps/dashboard/components/batch-payments/index.tsx @@ -214,7 +214,7 @@ export default function BatchPayments() { > {modalDetails.heading} -

{modalDetails.message}

+

{modalDetails.message}

@@ -250,7 +250,11 @@ export default function BatchPayments() { label="Balance in USD Wallet" value={`$${centsToDollars(paymentDetails.userWalletBalance.USD)} USD`} /> - diff --git a/apps/dashboard/components/upload-button.tsx b/apps/dashboard/components/upload-button.tsx index f73086c127..b40ba6d45f 100644 --- a/apps/dashboard/components/upload-button.tsx +++ b/apps/dashboard/components/upload-button.tsx @@ -99,14 +99,13 @@ export default function FileUpload({ {file ? file.name : "Upload a csv file"} { + beforeEach(() => { + cy.viewport(1920, 1080) + cy.setCookie("next-auth.session-token", testData.NEXT_AUTH_SESSION_TOKEN, { + secure: true, + }) + cy.visit("/batch-payments") + }) + + it("Batch Payments Test", () => { + cy.get("[data-testid=csv-upload-input]").selectFile("cypress/fixtures/template.csv") + + cy.get("[data-testid=confirm-batch-payments-btn]").should("exist") + cy.get("[data-testid=confirm-batch-payments-btn]").should("be.visible") + cy.get("[data-testid=confirm-batch-payments-btn]").should("not.be.disabled") + cy.get("[data-testid=confirm-batch-payments-btn]").click() + + cy.get("[data-testid=batch-payments-modal-message]").should( + "have.text", + "Batch Payment Completed", + ) + + cy.loginAndGetToken(testData.PHONE, testData.CODE).then((token) => { + const authToken = token + cy.getTransactions(authToken, 4).then((transactions) => { + // Check for specific BTC transactions + btcPaymentInUSDCurrency.forEach((expectedBtcTransaction) => { + const found = transactions.some( + (transaction) => + transaction.settlementCurrency === + expectedBtcTransaction.settlementCurrency && + transaction.status === expectedBtcTransaction.status && + transaction.settlementDisplayAmount === + expectedBtcTransaction.settlementDisplayAmount, + ) + expect(found).to.be.true + }) + + expectedTransactions.forEach((expectedTransaction) => { + if ( + !btcPaymentInUSDCurrency.some( + (btcTx) => + btcTx.settlementCurrency === expectedTransaction.settlementCurrency && + btcTx.status === expectedTransaction.status && + btcTx.settlementDisplayAmount === + expectedTransaction.settlementDisplayAmount, + ) + ) { + expect(transactions).to.deep.include(expectedTransaction) + } + }) + }) + }) + }) +}) diff --git a/apps/dashboard/cypress/e2e/e2e.cy.ts b/apps/dashboard/cypress/e2e/e2e.cy.ts index b61ad20636..fe7d3f4e9c 100644 --- a/apps/dashboard/cypress/e2e/e2e.cy.ts +++ b/apps/dashboard/cypress/e2e/e2e.cy.ts @@ -1,3 +1,4 @@ /* eslint-disable import/no-unassigned-import */ import "./api-keys/api-keys.cy" import "./callback/callback.cy" +import "./batch-payments/batch-payments.cy" diff --git a/apps/dashboard/cypress/fixtures/template.csv b/apps/dashboard/cypress/fixtures/template.csv new file mode 100644 index 0000000000..088c09c2fa --- /dev/null +++ b/apps/dashboard/cypress/fixtures/template.csv @@ -0,0 +1,5 @@ +username,amount,currency,wallet,memo +test_user_a,12,USD,USD,Testing to send 12 USD +test_user_b,10,USD,USD,Testing to send 10 USD +test_user_c,5,USD,BTC,Testing to send 5 USD +test_user_a,100,SATS,BTC,Testing to send 100 SATS diff --git a/apps/dashboard/cypress/support/commands.ts b/apps/dashboard/cypress/support/commands.ts index b5f5060e04..d01aa62e1d 100644 --- a/apps/dashboard/cypress/support/commands.ts +++ b/apps/dashboard/cypress/support/commands.ts @@ -1,40 +1,12 @@ /// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// -// declare global { -// namespace Cypress { -// interface Chainable { -// login(email: string, password: string): Chainable -// drag(subject: string, options?: Partial): Chainable -// dismiss(subject: string, options?: Partial): Chainable -// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable -// } -// } -// } +import { CORE_URL } from "./test-data" + +type Transaction = { + settlementAmount: number + settlementCurrency: string + status: string + settlementDisplayAmount: string +} // eslint-disable-next-line @typescript-eslint/no-namespace declare namespace Cypress { @@ -43,6 +15,11 @@ declare namespace Cypress { getOTP(email: string): Chainable requestEmailCode(email: string): Chainable flushRedis(): Chainable + loginAndGetToken(phone: string, code: string): Chainable + getTransactions( + authToken: string, + numberOfTransactions: number, + ): Chainable> } } @@ -69,3 +46,56 @@ Cypress.Commands.add("flushRedis", () => { } }) }) + +Cypress.Commands.add("loginAndGetToken", (phone, code) => { + cy.flushRedis() + cy.request({ + method: "POST", + url: `${CORE_URL}/auth/phone/login`, + body: { + phone, + code, + }, + }).then((response) => { + expect(response.body).to.have.property("authToken") + return response.body.authToken + }) +}) + +Cypress.Commands.add("getTransactions", (authToken, numberOfTransactions) => { + cy.request({ + method: "POST", + url: `${CORE_URL}/graphql`, + headers: { + "Authorization": `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + body: { + query: ` + query GetFirstTransactions($first: Int!) { + me { + defaultAccount { + transactions(first: $first) { + edges { + node { + settlementAmount + settlementCurrency + status + settlementDisplayAmount + } + } + } + } + } + } + `, + variables: { + first: numberOfTransactions, + }, + }, + }).then((response) => { + return response.body.data.me.defaultAccount.transactions.edges.map( + (edge: { node: Transaction }) => edge.node, + ) + }) +}) diff --git a/apps/dashboard/cypress/support/test-data.ts b/apps/dashboard/cypress/support/test-data.ts index ea00897c80..d82bc8f2ba 100644 --- a/apps/dashboard/cypress/support/test-data.ts +++ b/apps/dashboard/cypress/support/test-data.ts @@ -2,4 +2,37 @@ export const testData = { NEXT_AUTH_SESSION_TOKEN: Cypress.env("NEXT_AUTH_SESSION_TOKEN"), EMAIL: "test@galoy.io", CALLBACK_URL: "https://www.google.com/", + PHONE: "+16505554350", + CODE: "000000", } + +export const CORE_URL = "http://localhost:4455" + +export const expectedTransactions = [ + { + settlementAmount: -100, + settlementCurrency: "BTC", + status: "SUCCESS", + settlementDisplayAmount: "-0.04", + }, + { + settlementAmount: -1000, + settlementCurrency: "USD", + status: "SUCCESS", + settlementDisplayAmount: "-10.00", + }, + { + settlementAmount: -1200, + settlementCurrency: "USD", + status: "SUCCESS", + settlementDisplayAmount: "-12.00", + }, +] + +export const btcPaymentInUSDCurrency = [ + { + settlementCurrency: "BTC", + status: "SUCCESS", + settlementDisplayAmount: "-5.00", + }, +] diff --git a/dev/BUCK b/dev/BUCK index 74a452881f..00fca6d337 100644 --- a/dev/BUCK +++ b/dev/BUCK @@ -87,3 +87,13 @@ sh_binary( name = "stoppable-trigger", main = "bin/run-stoppable-trigger.sh", ) + +sh_binary( + name = "add-test-users-with-usernames", + main = "bin/add-test-users-with-usernames.sh", +) + +sh_binary( + name = "fund-user", + main = "bin/fund-user.sh", +) diff --git a/dev/Tiltfile b/dev/Tiltfile index 35c97d9665..074aca6721 100644 --- a/dev/Tiltfile +++ b/dev/Tiltfile @@ -61,6 +61,8 @@ local_resource( "api-keys", "svix", "svix-pg", + "add-test-users-with-usernames", + "fund-user", ], links = [ link("http://localhost:3001", "dashboard"), @@ -162,6 +164,36 @@ local_resource( ] ) +local_resource( + name='add-test-users-with-usernames', + labels = ['test'], + cmd='buck2 run //dev:add-test-users-with-usernames', + allow_parallel = True, + resource_deps = [ + "oathkeeper", + "kratos", + "api", + ] +) + +local_resource( + name='fund-user', + labels = ['test'], + cmd='buck2 run //dev:fund-user', + allow_parallel = True, + resource_deps = [ + "oathkeeper", + "kratos", + "api", + "init-onchain", + "init-test-user", + "api-trigger", + "stablesats", + "price", + ] +) + + consent_target = "//apps/consent:dev" if is_ci: consent_target = '//apps/consent:consent' diff --git a/dev/bin/add-test-users-with-usernames.sh b/dev/bin/add-test-users-with-usernames.sh index dcb4700d56..e0f4080cdd 100755 --- a/dev/bin/add-test-users-with-usernames.sh +++ b/dev/bin/add-test-users-with-usernames.sh @@ -31,7 +31,7 @@ echo "Starting main execution" echo "Logging in user $user_a" auth_token_a=$(login_user "${user_a}") -echo "Auth token for $user_a: $auth_token_a" +echo "Auth token for $user_a: $auth_token_a" echo "Logging in user $user_b" auth_token_b=$(login_user "${user_b}") @@ -45,4 +45,4 @@ update_username "$auth_token_a" "$user_a_username" update_username "$auth_token_b" "$user_b_username" update_username "$auth_token_c" "$user_c_username" -echo "Username update process completed +echo "Username update process completed" diff --git a/dev/bin/fund-user.sh b/dev/bin/fund-user.sh new file mode 100755 index 0000000000..74f52b131a --- /dev/null +++ b/dev/bin/fund-user.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# set -e +# set -x + +echo "Setting up DEV_DIR variable" +DEV_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" +source "${DEV_DIR}/helpers/auth.sh" +source "${DEV_DIR}/helpers/onchain.sh" + +user_phone="+16505554350" +token="$(login_user "${user_phone}")" +echo "Fetching wallets for account" + +echo "Funding wallets" +fund_user_onchain "$token" "USD" +fund_user_onchain "$token" "BTC" diff --git a/dev/helpers/gql/on-chain-address-create.gql b/dev/helpers/gql/on-chain-address-create.gql new file mode 100644 index 0000000000..b8992d2763 --- /dev/null +++ b/dev/helpers/gql/on-chain-address-create.gql @@ -0,0 +1,8 @@ +mutation onChainAddressCreate($input: OnChainAddressCreateInput!) { + onChainAddressCreate(input: $input) { + address + errors { + message + } + } +} diff --git a/dev/helpers/gql/transactions.gql b/dev/helpers/gql/transactions.gql new file mode 100644 index 0000000000..5955d7a311 --- /dev/null +++ b/dev/helpers/gql/transactions.gql @@ -0,0 +1,66 @@ +query transactions($walletIds: [WalletId], $first: Int, $after: String) { + me { + defaultAccount { + defaultWallet { + id + } + transactions(walletIds: $walletIds, first: $first, after: $after) { + ...TransactionList + } + } + } +} + +fragment TransactionList on TransactionConnection { + pageInfo { + hasNextPage + } + edges { + cursor + node { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + paymentRequest + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } +} diff --git a/dev/helpers/gql/wallets-for-account.gql b/dev/helpers/gql/wallets-for-account.gql new file mode 100644 index 0000000000..4679226643 --- /dev/null +++ b/dev/helpers/gql/wallets-for-account.gql @@ -0,0 +1,13 @@ +query me { + me { + defaultAccount { + id + wallets { + id + walletCurrency + balance + pendingIncomingBalance + } + } + } +} diff --git a/dev/helpers/onchain.sh b/dev/helpers/onchain.sh new file mode 100644 index 0000000000..f521d08496 --- /dev/null +++ b/dev/helpers/onchain.sh @@ -0,0 +1,59 @@ +DEV_DIR="$(dirname "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")")" +source "${DEV_DIR}/helpers/gql.sh" +source "${DEV_DIR}/helpers/cli.sh" + + +fund_user_onchain() { + local token=$1 + local wallet_currency=$2 + local btc_amount_in_btc=${3:-"0.01"} + + response="$(exec_graphql "$token" "wallets-for-account")" + wallet_id=$(echo "$response" | jq -r --arg wc "$wallet_currency" '.data.me.defaultAccount.wallets[] | select(.walletCurrency == $wc) .id') + + echo "Creating variables for GraphQL query for :====> $wallet_id wallet ID i.e :===> $wallet_currency" + variables=$( + jq -n \ + --arg wallet_id "$wallet_id" \ + '{input: {walletId: $wallet_id}}' + ) + + response=$(exec_graphql "$token" 'on-chain-address-create' "$variables") + address=$(echo "$response" | jq -r '.data.onChainAddressCreate.address') + [[ "${address}" != "null" ]] || exit 1 + + + bitcoin_cli -regtest loadwallet "outside" + bitcoin_cli -regtest listwallets + bitcoin_cli -regtest sendtoaddress "$address" "$btc_amount_in_btc" + bitcoin_cli -regtest -generate 4 + + variables=$( + jq -n \ + --argjson first "1" \ + '{"first": $first}' + ) + local success=false + for i in {1..60}; do + response=$(exec_graphql "$token" 'transactions' "$variables") + jq_query='.data.me.defaultAccount.transactions.edges[] | select(.node.initiationVia.address == $address) .node' + transaction_info=$(echo $response \ + | jq -r --arg address "$address" "$jq_query") + + settled_status=$(echo "$transaction_info" | jq -r ".status") + settled_currency=$(echo "$transaction_info" | jq -r ".settlementCurrency") + + if [[ "${settled_status}" == "SUCCESS" && "${settled_currency}" == "$wallet_currency" ]]; then + echo "Transaction successful with correct settlement currency" + success=true + break + fi + + sleep 1 + done + + if [[ $success != true ]]; then + echo "Failed to fund user: response ==> $response" + exit 1 + fi +}