Skip to content

Commit

Permalink
Using PAPI for e2e tests
Browse files Browse the repository at this point in the history
* At first, e2e types were generated on postinstall and prebuild steps,
but that required metadata to be available in Docker image, even though
it's not technically used in the application code.
Making e2e types generation a manual step together with excluding tests
code from Docker image solved it better.
* .scale metadata files from Zombienet hosts saved in the codebase,
in order to be able to compile the code separately from running Zombienet
* Upgraded both Zombienet and Polkadot in the CI, as well as the docs
for e2e tests.
* Used persistent path for zombienet nodes' logs, and printing them in
CI in case of a failure.
  • Loading branch information
mutantcornholio committed Mar 13, 2024
1 parent b644692 commit 990034e
Show file tree
Hide file tree
Showing 10 changed files with 668 additions and 68 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
!tsconfig.json
!env.*.config.json
!src

/src/**/*.e2e.ts
/src/**/*.spec.ts
25 changes: 20 additions & 5 deletions .github/workflows/E2E.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,41 @@ jobs:
- run: yarn install --frozen-lockfile
- name: Download Polkadot and parachain binaries
run: |
wget --no-verbose https://github.com/paritytech/cumulus/releases/download/v0.9.420/polkadot-parachain
wget --no-verbose https://github.com/paritytech/polkadot/releases/download/v0.9.42/polkadot
wget --no-verbose https://github.com/paritytech/polkadot-sdk/releases/download/polkadot-v1.8.0/polkadot
wget --no-verbose https://github.com/paritytech/polkadot-sdk/releases/download/polkadot-v1.8.0/polkadot-parachain
wget --no-verbose https://github.com/paritytech/polkadot-sdk/releases/download/polkadot-v1.8.0/polkadot-prepare-worker
wget --no-verbose https://github.com/paritytech/polkadot-sdk/releases/download/polkadot-v1.8.0/polkadot-execute-worker
chmod +x ./polkadot*
working-directory: e2e
- name: Run a local relaychain with a parachain using zombienet
run: |
export PATH=$(pwd):$PATH
npx --yes @zombienet/[email protected] \
--provider native spawn zombienet.native.toml \
npx --yes @zombienet/[email protected] \
--provider native \
--dir zombienet_logs \
spawn zombienet.native.toml \
> polkadot.txt 2>&1 &
source wait_until.sh 'curl -s "127.0.0.1:9933"'
source wait_until.sh 'curl -s "127.0.0.1:9934"'
working-directory: e2e
- name: Build e2e types
run: yarn generate:papi:e2e
- name: Build faucet
run: yarn build:docker
- name: Run the E2E tests
run: yarn test:e2e
- name: Debug Polkadot logs
- name: Debug Zombienet host logs
if: failure()
run: cat e2e/polkadot.txt
- name: Debug Zombienet alice node logs
if: failure()
run: cat e2e/zombienet_logs/alice.log
- name: Debug Zombienet bob node logs
if: failure()
run: cat e2e/zombienet_logs/bob.log
- name: Debug Zombienet alice-1 node logs
if: failure()
run: cat e2e/zombienet_logs/alice-1.log
- name: Debug Matrix logs
if: failure()
run: cat e2e/containter_logs/faucet-test-matrix.log
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ storage.db
sqlite.db
build
e2e/containter_logs
e2e/zombienet_logs

# Autogenerated
env.*.config.json.d.ts
!.env.example

/data
/src/test/codegen
11 changes: 9 additions & 2 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ command -v polkadot-parachain || echo "No polkadot-parachain in PATH"
Next, in the root of this repository, start the Zombienet:

```bash
npx --yes @zombienet/[email protected] --provider native spawn e2e/zombienet.native.toml
npx --yes @zombienet/[email protected] --provider native --dir e2e/zombienet_logs spawn e2e/zombienet.native.toml
```

Verify that it's working correctly by opening the [relaychain](https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9933#/explorer) and [parachain](https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9934#/explorer) explorers,
Expand All @@ -103,14 +103,21 @@ curl localhost:9934
yarn build:docker
```

4. Run the tests
4. Generate PAPI types for e2e tests

```bash
yarn generate:papi:e2e
```

5. Run the tests

```bash
yarn test:e2e
```

Logs of the application container will be avaiable at `e2e/containter_logs/faucet-test-app.log`
Logs of matrix container will be avaiable at `e2e/containter_logs/faucet-test-matrix.log`
Logs of zombienet nodes will be available at `e2e/zombienet_logs/`

The whole suite of tests can take tens of seconds,
because it depends on the blockchain to mine blocks and execute the XCM teleportation process.
Binary file added e2e/parachain.scale
Binary file not shown.
10 changes: 10 additions & 0 deletions e2e/polkadot-api-e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"relaychain": {
"outputFolder": "src/test/codegen",
"metadata": "e2e/relaychain.scale"
},
"parachain": {
"outputFolder": "src/test/codegen",
"metadata": "e2e/parachain.scale"
}
}
Binary file added e2e/relaychain.scale
Binary file not shown.
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"format": "prettier ./src ./client/src ./client/tests --check",
"format:fix": "prettier ./src ./client/src ./client/tests --write",
"generate:types": "echo \"declare const schema: $(cat env.faucet.config.json); export default schema;\" > env.faucet.config.json.d.ts",
"generate:papi:e2e": "papi generate --config e2e/polkadot-api-e2e.json",
"lint": "eslint ./src/ ./client/src ./client/tests --ext .js,.ts,.svelte",
"lint:fix": "eslint ./src/ ./client/src ./client/tests --ext .js,.ts,.svelte --fix",
"migrations:generate": "typeorm-ts-node-commonjs migration:generate -d src/db/dataSource.ts",
Expand All @@ -21,7 +22,7 @@
"prepare": "ts-patch install -s",
"start": "node ./build/src/start.js",
"test": "jest",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --es-module-specifier-resolution=node' jest -c jest.e2e.config.js --runInBand",
"test:e2e": "NODE_OPTIONS='--experimental-vm-modules --es-module-specifier-resolution=node' jest -c jest.e2e.config.js --runInBand --forceExit",
"typecheck": "tsc --noEmit"
},
"author": "",
Expand Down Expand Up @@ -49,6 +50,10 @@
},
"dependencies": {
"@eng-automation/js": "^1.0.3",
"@polkadot-api/cli": "^0.0.1-0027dd301d1d5a078e9c770a18e27aed76a66f50.1.0",
"@polkadot-api/client": "^0.0.1-0027dd301d1d5a078e9c770a18e27aed76a66f50.1.0",
"@polkadot-api/node-polkadot-provider": "^0.0.1-0027dd301d1d5a078e9c770a18e27aed76a66f50.1.0",
"@polkadot-api/ws-provider": "^0.0.1-0027dd301d1d5a078e9c770a18e27aed76a66f50.1.0",
"@polkadot/api": "^10.10.1",
"@polkadot/keyring": "^12.5.1",
"@polkadot/util": "^12.5.1",
Expand Down Expand Up @@ -83,6 +88,7 @@
"joi": "^17.6.4",
"lint-staged": "^12.3.8",
"nodemon": "^2.0.19",
"rxjs": "^7.8.1",
"simple-git-hooks": "^2.7.0",
"supertest": "^6.3.3",
"testcontainers": "^9.9.1",
Expand Down
104 changes: 46 additions & 58 deletions src/faucet.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { until, validatedFetch } from "@eng-automation/js";
import { ApiPromise } from "@polkadot/api";
import { createTestKeyring } from "@polkadot/keyring";
import { WsProvider } from "@polkadot/rpc-provider";
import { BN } from "@polkadot/util";
import { randomAsU8a } from "@polkadot/util-crypto";
import { AccountId, createClient } from "@polkadot-api/client";
import { getChain } from "@polkadot-api/node-polkadot-provider";
import { WebSocketProvider } from "@polkadot-api/ws-provider/node";
import crypto from "crypto";
import Joi from "joi";
import { filter, firstValueFrom, mergeMap, pairwise, race, skipWhile, throwError } from "rxjs";
import { Repository } from "typeorm";

import { Drip } from "src/db/entity/Drip";
import parachainDescriptors from "src/test/codegen/parachain";
import relaychainDescriptors from "src/test/codegen/relaychain";
import { drip } from "src/test/webhookHelpers";

import { getLatestMessage, postMessage } from "./test/matrixHelpers";
import { destroyDataSource, E2ESetup, getDataSource, setup, teardown } from "./test/setupE2E";

const randomAddress = () => createTestKeyring().addFromSeed(randomAsU8a(32)).address;
const randomAddress = () => AccountId().dec(crypto.randomBytes(32));
const sha256 = (x: string) => crypto.createHash("sha256").update(x, "utf8").digest("hex");

describe("Faucet E2E", () => {
Expand All @@ -26,21 +27,37 @@ describe("Faucet E2E", () => {
let e2eSetup: E2ESetup;
let dripRepository: Repository<Drip>;

const polkadotApi = new ApiPromise({
// Zombienet relaychain node.
provider: new WsProvider("ws://127.0.0.1:9933"),
types: { Address: "AccountId", LookupSource: "AccountId" },
});

const parachainApi = new ApiPromise({
// Zombienet parachain node.
provider: new WsProvider("ws://127.0.0.1:9934"),
types: { Address: "AccountId", LookupSource: "AccountId" },
});

const getUserBalance = async (userAddress: string, api: ApiPromise = polkadotApi) => {
const { data } = await api.query.system.account(userAddress);
return data.free.toBn();
const relaychainClient = createClient(
getChain({
provider: WebSocketProvider("ws://127.0.0.1:9933"),
keyring: [],
}),
);
const relayChainApi = relaychainClient.getTypedApi(relaychainDescriptors);

const parachainClient = createClient(
getChain({
provider: WebSocketProvider("ws://127.0.0.1:9934"),
keyring: [],
}),
);

const parachainApi = parachainClient.getTypedApi(parachainDescriptors);

type SomeApi = typeof relayChainApi | typeof parachainApi;

const expectBalanceIncrease = async (useraddress: string, api: SomeApi, blocksNum: number) => {
const startBlock = await api.query.System.Number.getValue({ at: "best" });
return await firstValueFrom(
race([
api.query.System.Account.watchValue(useraddress, "best")
.pipe(pairwise())
.pipe(filter(([oldValue, newValue]) => newValue.data.free > oldValue.data.free)),
api.query.System.Number.watchValue("best")
.pipe(skipWhile((blockNumber) => blockNumber - startBlock < blocksNum))
.pipe(mergeMap(() => throwError(() => new Error(`Balance did not increase in ${blocksNum} blocks`)))),
]),
);
};

beforeAll(async () => {
Expand All @@ -50,20 +67,15 @@ describe("Faucet E2E", () => {
matrixUrl = e2eSetup.matrixSetup.matrixUrl;
webEndpoint = e2eSetup.webEndpoint;

await polkadotApi.isReady;
await parachainApi.isReady;

console.log("Zombienet: done");

const AppDataSource = await getDataSource();
dripRepository = AppDataSource.getRepository(Drip);

console.log("beforeAll: done");
}, 100_000);

afterAll(async () => {
await polkadotApi.disconnect();
await parachainApi.disconnect();
relaychainClient.destroy();
parachainClient.destroy();
await destroyDataSource();
if (e2eSetup) teardown(e2eSetup);
});
Expand Down Expand Up @@ -91,7 +103,6 @@ describe("Faucet E2E", () => {

test("The bot drips to a given address", async () => {
const userAddress = randomAddress();
const initialBalance = await getUserBalance(userAddress);

await postMessage(matrixUrl, { roomId, accessToken: userAccessToken, body: `!drip ${userAddress}` });

Expand All @@ -104,17 +115,11 @@ describe("Faucet E2E", () => {
);
const botMessage = await getLatestMessage(matrixUrl, { roomId, accessToken: userAccessToken });
expect(botMessage.body).toContain("Sent @user:parity.io 10 UNITs.");
await until(
async () => (await getUserBalance(userAddress)).gt(initialBalance),
1000,
15,
"balance did not increase.",
);
await expectBalanceIncrease(userAddress, relayChainApi, 3);
});

test("The bot teleports to a given address", async () => {
const userAddress = randomAddress();
const initialBalance = await getUserBalance(userAddress, parachainApi);

await postMessage(matrixUrl, {
roomId,
Expand All @@ -131,13 +136,7 @@ describe("Faucet E2E", () => {
);
const botMessage = await getLatestMessage(matrixUrl, { roomId, accessToken: userAccessToken });
expect(botMessage.body).toContain("Sent @user:parity.io 10 UNITs.");

await until(
async () => (await getUserBalance(userAddress, parachainApi)).gt(initialBalance),
1000,
40,
"balance did not increase.",
);
await expectBalanceIncrease(userAddress, parachainApi, 3);
});

test("The bot fails on invalid chain id", async () => {
Expand Down Expand Up @@ -178,37 +177,26 @@ describe("Faucet E2E", () => {
}>(`${webEndpoint}/balance`, Joi.object({ balance: Joi.string() }), {});

expect("balance" in result).toBeTruthy();
expect(new BN(result.balance).gtn(0)).toBeTruthy();
expect(BigInt(result.balance) > 0n).toBeTruthy();
});

test("The web endpoint drips to a given address", async () => {
const userAddress = randomAddress();
const initialBalance = await getUserBalance(userAddress);

const result = await drip(webEndpoint, userAddress);

expect(result.hash).toBeTruthy();
await until(
async () => (await getUserBalance(userAddress)).gt(initialBalance),
500,
15,
"balance did not increase.",
);
await expectBalanceIncrease(userAddress, relayChainApi, 3);
});

test("The web endpoint teleports to a given address", async () => {
const userAddress = randomAddress();
const initialBalance = await getUserBalance(userAddress, parachainApi);

const result = await drip(webEndpoint, userAddress, "1000");

expect(result.hash).toBeTruthy();
await until(
async () => (await getUserBalance(userAddress, parachainApi)).gt(initialBalance),
1000,
40,
"balance did not increase.",
);

await expectBalanceIncrease(userAddress, parachainApi, 3);
});

test("The web endpoint fails on wrong parachain", async () => {
Expand Down
Loading

0 comments on commit 990034e

Please sign in to comment.