Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using PAPI for e2e tests #379

Merged
merged 1 commit into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ check-linting:
<<: *kubernetes-env
script:
- yarn --frozen-lockfile
- yarn generate:papi:e2e
- cd client && yarn --frozen-lockfile && cd ..
- yarn typecheck && yarn format && yarn lint

Expand Down
13 changes: 11 additions & 2 deletions e2e/README.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a note about how to obtain the *.scale files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, added

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,23 @@ curl localhost:9934
yarn build:docker
```

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

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

These types are generated based on `.scale` files in `e2e/` directory. To regenerate these files using live zombienet nodes, use `papi update --config e2e/polkadot-api-e2e.json` command.

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.
12 changes: 12 additions & 0 deletions e2e/polkadot-api-e2e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"relaychain": {
"outputFolder": "src/test/codegen",
"wsUrl": "ws://127.0.0.1:9933",
"metadata": "e2e/relaychain.scale"
},
"parachain": {
"outputFolder": "src/test/codegen",
"wsUrl": "ws://127.0.0.1:9934",
"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
Loading