Skip to content

Commit

Permalink
Implement e2e test flow, fix small bugs (#65)
Browse files Browse the repository at this point in the history
* Create simple e2e package

* Fix production issues and inconsistencies

* Implement e2e test flow and CI test

* Fix tests
  • Loading branch information
Siegrift authored Oct 2, 2023
1 parent 4fe829c commit f21927e
Show file tree
Hide file tree
Showing 31 changed files with 540 additions and 39 deletions.
33 changes: 32 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,41 @@ jobs:
run: pnpm run prettier:check && pnpm run eslint:check
- name: Test
run: pnpm run test
e2e:
runs-on: ubuntu-latest
name: E2e tests
steps:
- name: Clone repo
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8.8.0
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18.x'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm run build
- name: Build Docker images
run: pnpm run docker:build
- name: Start services
# Start the e2e services in the background and wait a small amount of time for them to start.
run: |
pnpm run --recursive --filter e2e start:data-provider-api &
pnpm run --recursive --filter e2e start:ci:signed-api &
pnpm run --recursive --filter e2e start:ci:pusher &
sleep 5
- name: Run e2e tests
run: pnpm run --recursive --filter e2e test:e2e

required-checks-passed:
name: All required checks passed
runs-on: ubuntu-latest
needs: [lint-build-test]
needs: [documentation, lint-build-test, e2e]
steps:
- run: exit 0
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ pusher.json
secrets.env
signed-api.json
.DS_Store

# Do not ignore e2e config files. These don't need to be listed on Dockerignore because e2e package is not dockerized.
!packages/e2e/**/pusher.json
!packages/e2e/**/secrets.env
!packages/e2e/**/.env
!packages/e2e/**/signed-api.json
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A monorepo for managing signed data. Consists of:
- [common](./packages/common/README.md) - An internal-only package with common types and utilities used by other
packages.
- [pusher](./packages/pusher/README.md) - A service for pushing data provider signed data.
- [e2e](./packages/e2e/README.md) - End to end test utilizing Mock API, pusher and signed API.

## Getting started

Expand Down
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"nodemon": "^3.0.1"
},
"dependencies": {
"@api3/promise-utils": "0.4.0",
"@api3/promise-utils": "^0.4.0",
"@aws-sdk/client-s3": "^3.421.0",
"dotenv": "^16.3.1",
"ethers": "^5.7.2",
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const startServer = (config: Config) => {
const result = await batchInsertData(req.body);
res.status(result.statusCode).header(result.headers).send(result.body);

logger.info('Responded to request "POST /"', result);
logger.debug('Responded to request "POST /"', result);
});

app.get('/', async (_req, res) => {
Expand All @@ -23,7 +23,7 @@ export const startServer = (config: Config) => {
const result = await listAirnodeAddresses();
res.status(result.statusCode).header(result.headers).send(result.body);

logger.info('Responded to request "GET /"', result);
logger.debug('Responded to request "GET /"', result);
});

for (const endpoint of config.endpoints) {
Expand All @@ -36,7 +36,7 @@ export const startServer = (config: Config) => {
const result = await getData(req.params.airnodeAddress, delaySeconds);
res.status(result.statusCode).header(result.headers).send(result.body);

logger.info('Responded to request "GET /:airnode"', result);
logger.debug('Responded to request "GET /:airnode"', result);
});
}

Expand Down
6 changes: 4 additions & 2 deletions packages/common/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# common

Utilities commonly used by other packages. Each common utility lives in its own folder together with its documentation.
The implementation is re-exported by main entry of this package. The package consists of:
> Utilities commonly used by other packages.
Each common utility lives in its own folder together with its documentation. The implementation is re-exported by main
entry of this package. The package consists of:

- [logger](./src/logger) - Backend-only logger for Node.js packages based on Winston logger.

Expand Down
10 changes: 5 additions & 5 deletions packages/common/src/logger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ export interface Logger {
// That's causing an override of fields `name` and `message` if they are present.
const wrapper = (logger: Logger): Logger => {
return {
debug: (message, context) => logger.debug(message, { context }),
info: (message, context) => logger.info(message, { context }),
warn: (message, context) => logger.warn(message, { context }),
debug: (message, context) => logger.debug(message, context ? { context } : undefined),
info: (message, context) => logger.info(message, context ? { context } : undefined),
warn: (message, context) => logger.warn(message, context ? { context } : undefined),
// We need to handle both overloads of the `error` function
error: (message, errorOrContext, context) => {
if (errorOrContext instanceof Error) {
logger.error(message, errorOrContext, { context });
logger.error(message, errorOrContext, context ? { context } : undefined);
} else {
logger.error(message, { context: errorOrContext });
logger.error(message, errorOrContext ? { context: errorOrContext } : undefined);
}
},
child: (options) => wrapper(logger.child(options)),
Expand Down
15 changes: 15 additions & 0 deletions packages/e2e/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# NOTE: Keep in sync with .dockerignore
.build
.env
.idea
.log
.tsbuildinfo
.vscode
build
dist
node_modules
coverage
pusher.json
secrets.env
signed-api.json
.DS_Store
15 changes: 15 additions & 0 deletions packages/e2e/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# NOTE: Keep in sync with .dockerignore
.build
.env
.idea
.log
.tsbuildinfo
.vscode
build
dist
node_modules
coverage
pusher.json
secrets.env
signed-api.json
.DS_Store
26 changes: 26 additions & 0 deletions packages/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# e2e

> End to end test utilizing Mock API, pusher and signed API.
## Getting started

1. If you are using Docker Desktop, you need to change the URL in `pusher/secrets.env` from `localhost` to
`host.docker.internal`, because pusher is running inside a Docker container.
2. Build the latest Docker images. Run `pnpm run docker:build` from the monorepo root. The e2e flow uses the docker
images.
3. This module contains services (or configurations) that are integrated together. Specifically:

- `pusher` - Contains the configuration for the pusher service.
- `signed-api` - Contains the configuration for the signed API service.
- `data-provider-api.ts` - Contains the configuration for the data provider API service (mocked express server).
- `user.ts` - Contains the configuration for the user service (infinite fetch from signed API).

You are free to modify the configurations to test different scenarios.

4. There are `start:<some-service>` scripts to start the services. It is recommended to start each service in a separate
terminal and in this order:

1. `pnpm run start:data-provider-api`
2. `pnpm run start:signed-api`
3. `pnpm run start:pusher`
4. `pnpm run start:user`
5 changes: 5 additions & 0 deletions packages/e2e/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const config = require('../../jest.config');

module.exports = {
...config,
};
38 changes: 38 additions & 0 deletions packages/e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "e2e",
"version": "1.0.0",
"engines": {
"node": "^18.14.0",
"pnpm": "^8.8.0"
},
"scripts": {
"build": "tsc --project tsconfig.build.json",
"clean": "rm -rf coverage dist",
"eslint:check": "eslint . --ext .js,.ts --max-warnings 0",
"eslint:fix": "eslint . --ext .js,.ts --fix",
"prettier:check": "prettier --check \"./**/*.{js,ts,md,yml,json}\"",
"prettier:fix": "prettier --write \"./**/*.{js,ts,md,yml,json}\"",
"start:data-provider-api": "ts-node src/data-provider-api.ts",
"start:pusher": "docker run -it --init --volume $(pwd)/src/pusher:/app/config --network host --env-file ./src/pusher/.env --rm --memory=256m pusher:latest",
"start:signed-api": "docker run --publish 8090:8090 -it --init --volume $(pwd)/src/signed-api:/app/config --env-file ./src/signed-api/.env --rm --memory=256m api:latest",
"start:ci:pusher": "docker run --init --volume $(pwd)/src/pusher:/app/config --network host --env-file ./src/pusher/.env --rm --memory=256m pusher:latest",
"start:ci:signed-api": "docker run --publish 8090:8090 --init --volume $(pwd)/src/signed-api:/app/config --env-file ./src/signed-api/.env --rm --memory=256m api:latest",
"start:user": "ts-node src/user.ts",
"test:e2e": "jest",
"tsc": "tsc --project ."
},
"license": "MIT",
"dependencies": {
"@api3/promise-utils": "^0.4.0",
"axios": "^1.5.1",
"ethers": "^5.7.2",
"express": "^4.18.2",
"lodash": "^4.17.21",
"signed-api/common": "workspace:common@*",
"zod": "^3.22.2"
},
"devDependencies": {
"@types/express": "^4.17.18",
"@types/lodash": "^4.14.199"
}
}
52 changes: 52 additions & 0 deletions packages/e2e/src/data-provider-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import express from 'express';
import { logger } from './logger';

const app = express();
const PORT = 9876 || process.env.PORT;

interface Asset {
value: number;
// Everytime the API is queried, the value will be updated by a random percentage.
deltaPercent: number;
name: string;
}

const assets: Asset[] = [
{
value: 1000,
deltaPercent: 10,
name: 'MOCK-ETH/USD',
},
{
value: 5000,
deltaPercent: 2,
name: 'MOCK-BTC/USD',
},
{
value: 750,
deltaPercent: 80,
name: 'MOCK-ABC/DEF',
},
{
value: 50000,
deltaPercent: 20,
name: 'MOCK-HJK/KOP',
},
];

app.get('/', (_req, res) => {
logger.debug('Request GET /');

for (const asset of assets) {
asset.value = parseFloat((asset.value * (1 + ((Math.random() - 0.5) * asset.deltaPercent) / 100)).toFixed(5));
}

const response = Object.fromEntries(assets.map((asset) => [asset.name, asset.value]));
logger.debug('Response GET /', response);

res.json(response);
});

app.listen(PORT, () => {
logger.info(`Server is running on http://localhost:${PORT}`);
});
8 changes: 8 additions & 0 deletions packages/e2e/src/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createLogger } from 'signed-api/common';

export const logger = createLogger({
colorize: true,
enabled: true,
minLevel: 'debug',
format: 'pretty',
});
4 changes: 4 additions & 0 deletions packages/e2e/src/pusher/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
LOGGER_ENABLED=true
LOG_COLORIZE=true
LOG_FORMAT=pretty
LOG_LEVEL=debug
Loading

0 comments on commit f21927e

Please sign in to comment.