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

Implement e2e test flow, fix small bugs #65

Merged
merged 4 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
32 changes: 31 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,40 @@ 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 start:data-provider-api &
pnpm run start:signed-api &
pnpm run start:pusher &
sleep 5
- name: Run E2E Tests
run: pnpm run 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);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was too spammy. Note, that I still left the receive log as INFO level.

});

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.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To make it consistent with 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),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If you do not pass context, the logs previously had {context: undefined} which is unnecessary (and confusing).

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.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since the configurations are not gitignored it will modify source tree, but I don't think we need to care much.

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,
};
37 changes: 37 additions & 0 deletions packages/e2e/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"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 --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:user": "ts-node src/user.ts",
"test": "jest --passWithNoTests",
"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
Loading