Skip to content

Commit

Permalink
Merge pull request #149 from matter-labs/QA-582-retry-wrapper-over-AP…
Browse files Browse the repository at this point in the history
…I-requests

chore: retry wrapper over api requests
  • Loading branch information
abilevych authored Jan 24, 2024
2 parents 8a57793 + d23bd9b commit eb0e8b6
Show file tree
Hide file tree
Showing 11 changed files with 1,193 additions and 1,662 deletions.
4 changes: 3 additions & 1 deletion packages/integration-tests/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ export const localConfig = {
standardTimeout: 60 * 1000,
extendedPause: 20 * 1000,
standardPause: 5 * 1000,
minimalPause: 1 * 1000,
minimalPause: 0.5 * 1000,
maxAPIretries: 200,
intervalAPIretries: 0.2 * 1000,
};

export const environment = {
Expand Down
33 changes: 32 additions & 1 deletion packages/integration-tests/src/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { execSync } from "child_process";
import { ethers } from "ethers";
import { promises as fs } from "fs";
import * as path from "path";
import * as request from "supertest";
import { Provider } from "zksync-web3";

import { localConfig } from "./config";
import { environment, localConfig } from "./config";
import { Logger } from "./entities";

import type { BaseProvider } from "@ethersproject/providers/src.ts/base-provider";
Expand Down Expand Up @@ -55,4 +56,34 @@ export class Helper {
}
return ethers.utils.formatUnits(await provider.getBalance(walletAddress), "wei");
}

async delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async performGETrequest(apiRoute: string) {
return request(environment.blockExplorerAPI).get(apiRoute);
}

/**
* A retry wrapper method to enhance test stability in API testing.
* Useful when API response fields may not immediately reflect the expected state,
* but can update to the correct response after a delay.
* Attempts to execute the action a specified number of times (defined in localConfig.maxAPIretries)
* with a delay between attempts (localConfig.intervalAPIretries).
* Throws an error if the action consistently fails after all retries.
*/
async retryTestAction(action) {
for (let i = 0; i < localConfig.maxAPIretries; i++) {
try {
await action();
return;
} catch (error) {
if (i === localConfig.maxAPIretries - 1) {
throw error;
}
await this.delay(localConfig.intervalAPIretries);
}
}
}
}
150 changes: 73 additions & 77 deletions packages/integration-tests/tests/api/accounts.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as request from "supertest";
import { setTimeout } from "timers/promises";

import { environment } from "../../src/config";
import { localConfig } from "../../src/config";
Expand All @@ -10,100 +9,97 @@ describe("API module: Account", () => {
jest.setTimeout(localConfig.standardTimeout);

const helper = new Helper();
let apiRoute: string;
let response;

//@id1704
it("Verify /api?module=account&action=balancemulti response", async () => {
const apiRoute = `/api?module=account&action=balancemulti&address=${Wallets.richWalletAddress},${Wallets.mainWalletAddress}`;
const richWalletBalance = await helper.getBalanceETH(Wallets.richWalletAddress, "L2");
const mainWalletBalance = await helper.getBalanceETH(Wallets.mainWalletAddress, "L2");
const richWalletLowerCase = Wallets.richWalletAddress.toLowerCase();
const mainWalletLowerCase = Wallets.mainWalletAddress.toLowerCase();
await setTimeout(localConfig.extendedPause); //works unstable without timeout
await helper.retryTestAction(async () => {
apiRoute = `/api?module=account&action=balancemulti&address=${Wallets.richWalletAddress},${Wallets.mainWalletAddress}`;
const richWalletBalance = await helper.getBalanceETH(Wallets.richWalletAddress, "L2");
const mainWalletBalance = await helper.getBalanceETH(Wallets.mainWalletAddress, "L2");
const richWalletLowerCase = Wallets.richWalletAddress.toLowerCase();
const mainWalletLowerCase = Wallets.mainWalletAddress.toLowerCase();
response = await helper.performGETrequest(apiRoute);

return request(environment.blockExplorerAPI)
.get(apiRoute)
.expect(200)
.expect((res) => expect(res.body.result.length).toBeGreaterThan(1))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ status: "1" })))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ message: "OK" })))
.expect((res) =>
expect(res.body.result[0]).toStrictEqual(
expect.objectContaining({ account: richWalletLowerCase, balance: richWalletBalance })
)
)
.expect((res) =>
expect(res.body.result[1]).toStrictEqual(
expect.objectContaining({ account: mainWalletLowerCase, balance: mainWalletBalance })
)
expect(response.status).toBe(200);
expect(response.body.result.length).toBeGreaterThan(1);
expect(response.body).toStrictEqual(expect.objectContaining({ status: "1" }));
expect(response.body).toStrictEqual(expect.objectContaining({ message: "OK" }));
expect(response.body.result[0]).toStrictEqual(
expect.objectContaining({ account: richWalletLowerCase, balance: richWalletBalance })
);
expect(response.body.result[1]).toStrictEqual(
expect.objectContaining({ account: mainWalletLowerCase, balance: mainWalletBalance })
);
});
});

//@id1703
it("Verify /api?module=account&action=balance response", async () => {
const apiRoute = `/api?module=account&action=balance&address=${Wallets.richWalletAddress}`;
const balance = await helper.getBalanceETH(Wallets.richWalletAddress, "L2");
await setTimeout(localConfig.extendedPause); //works unstable without timeout
await helper.retryTestAction(async () => {
const balance = await helper.getBalanceETH(Wallets.richWalletAddress, "L2");
apiRoute = `/api?module=account&action=balance&address=${Wallets.richWalletAddress}`;
response = await helper.performGETrequest(apiRoute);

return request(environment.blockExplorerAPI)
.get(apiRoute)
.expect(200)
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ status: "1" })))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ message: "OK" })))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ result: balance })));
expect(response.status).toBe(200);
expect(response.body).toStrictEqual(expect.objectContaining({ status: "1" }));
expect(response.body).toStrictEqual(expect.objectContaining({ message: "OK" }));
expect(response.body).toStrictEqual(expect.objectContaining({ result: balance }));
});
});

//@id1705
it("Verify /api?module=account&action=tokenbalance response", async () => {
const apiRoute = `/api?module=account&action=tokenbalance&contractaddress=${Token.ETHER_ERC20_Address}&address=${Wallets.richWalletAddress}`;
await setTimeout(localConfig.extendedPause); //works unstable without timeout
await helper.retryTestAction(async () => {
apiRoute = `/api?module=account&action=tokenbalance&contractaddress=${Token.ETHER_ERC20_Address}&address=${Wallets.richWalletAddress}`;
response = await helper.performGETrequest(apiRoute);

return request(environment.blockExplorerAPI)
.get(apiRoute)
.expect(200)
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ status: "1" })))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ message: "OK" })))
.expect((res) => expect(typeof res.body.result).toStrictEqual("string"));
expect(response.status).toBe(200);
expect(response.body).toStrictEqual(expect.objectContaining({ status: "1" }));
expect(response.body).toStrictEqual(expect.objectContaining({ message: "OK" }));
expect(typeof response.body.result).toStrictEqual("string");
});
});

//@id1702
it("Verify /api?module=account&action=txlist response", async () => {
const blocks = await request(environment.blockExplorerAPI).get("/blocks");

const blockNumber = blocks.body.items[0].number;
const apiRoute = `/api?module=account&action=txlist&page=1&offset=10&sort=desc&endblock${blockNumber}&startblock=0&address=${Wallets.richWalletAddress}`;

await setTimeout(localConfig.extendedPause); //works unstable without timeout
await helper.retryTestAction(async () => {
const blocks = await request(environment.blockExplorerAPI).get("/blocks");
const blockNumber = blocks.body.items[0].number;
apiRoute = `/api?module=account&action=txlist&page=1&offset=10&sort=desc&endblock${blockNumber}&startblock=0&address=${Wallets.richWalletAddress}`;
response = await helper.performGETrequest(apiRoute);

return request(environment.blockExplorerAPI)
.get(apiRoute)
.expect(200)
.expect((res) => expect(res.body.result.length).toBeGreaterThan(1))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ status: "1" })))
.expect((res) => expect(res.body).toStrictEqual(expect.objectContaining({ message: "OK" })))
.expect((res) => expect(typeof res.body.result[0].blockNumber).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].timeStamp).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].hash).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].nonce).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].blockHash).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].transactionIndex).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].from).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].to).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].value).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].gas).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].gasPrice).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].isError).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].txreceipt_status).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].input).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].contractAddress).toBeTruthy()) // can be null
.expect((res) => expect(typeof res.body.result[0].cumulativeGasUsed).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].gasUsed).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].confirmations).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].fee).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].commitTxHash).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].proveTxHash).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].executeTxHash).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].isL1Originated).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].l1BatchNumber).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].methodId).toStrictEqual("string"))
.expect((res) => expect(typeof res.body.result[0].functionName).toStrictEqual("string"));
expect(response.status).toBe(200);
expect(response.body.result.length).toBeGreaterThan(1);
expect(response.body).toStrictEqual(expect.objectContaining({ status: "1" }));
expect(response.body).toStrictEqual(expect.objectContaining({ message: "OK" }));
expect(typeof response.body.result[0].blockNumber).toStrictEqual("string");
expect(typeof response.body.result[0].timeStamp).toStrictEqual("string");
expect(typeof response.body.result[0].hash).toStrictEqual("string");
expect(typeof response.body.result[0].nonce).toStrictEqual("string");
expect(typeof response.body.result[0].blockHash).toStrictEqual("string");
expect(typeof response.body.result[0].transactionIndex).toStrictEqual("string");
expect(typeof response.body.result[0].from).toStrictEqual("string");
expect(typeof response.body.result[0].to).toStrictEqual("string");
expect(typeof response.body.result[0].value).toStrictEqual("string");
expect(typeof response.body.result[0].gas).toStrictEqual("string");
expect(typeof response.body.result[0].gasPrice).toStrictEqual("string");
expect(typeof response.body.result[0].isError).toStrictEqual("string");
expect(typeof response.body.result[0].txreceipt_status).toStrictEqual("string");
expect(typeof response.body.result[0].input).toStrictEqual("string");
expect(typeof response.body.result[0].contractAddress).toBeTruthy(); // can be null
expect(typeof response.body.result[0].cumulativeGasUsed).toStrictEqual("string");
expect(typeof response.body.result[0].gasUsed).toStrictEqual("string");
expect(typeof response.body.result[0].confirmations).toStrictEqual("string");
expect(typeof response.body.result[0].fee).toStrictEqual("string");
expect(typeof response.body.result[0].commitTxHash).toStrictEqual("string");
expect(typeof response.body.result[0].proveTxHash).toStrictEqual("string");
expect(typeof response.body.result[0].executeTxHash).toStrictEqual("string");
expect(typeof response.body.result[0].isL1Originated).toStrictEqual("string");
expect(typeof response.body.result[0].l1BatchNumber).toStrictEqual("string");
expect(typeof response.body.result[0].methodId).toStrictEqual("string");
expect(typeof response.body.result[0].functionName).toStrictEqual("string");
});
});
});
Loading

0 comments on commit eb0e8b6

Please sign in to comment.