integration tests

This commit is contained in:
Gsk54 2023-01-05 16:17:37 +01:00
parent 6a08a90f02
commit ab9bb4897e
7 changed files with 280 additions and 56 deletions

View File

@ -1,18 +1,11 @@
# SERVICE
SERVICE_CONTAINER=v3-user
SERVICE_URL=0.0.0.0
SERVICE_PORT=5001
# PRISMA
DATABASE_URL="postgresql://user:user@v3-user-db:5432/user?schema=public"
# RABBIT MQ
RMQ_URI=amqp://localhost:5672
RMQ_URI=amqp://v3-broker:5672
# POSTGRES
POSTGRES_CONTAINER=v3-user-db
POSTGRES_IMAGE=postgres:15.0
POSTGRES_DB=user
POSTGRES_PASSWORD=user
POSTGRES_USER=user
POSTGRES_PORT=5501

11
.env.test Normal file
View File

@ -0,0 +1,11 @@
# SERVICE
SERVICE_URL=0.0.0.0
# PRISMA
DATABASE_URL="postgresql://user:user@localhost:5601/user?schema=public"
# RABBIT MQ
RMQ_URI=amqp://v3-broker:5672
# POSTGRES
POSTGRES_IMAGE=postgres:15.0

View File

@ -6,25 +6,35 @@ User-related data management.
You need [Docker](https://docs.docker.com/engine/) and its [compose](https://docs.docker.com/compose/) plugin.
You also need NodeJS installed locally : we **strongly** advise to install [Node Version Manager](https://github.com/nvm-sh/nvm) and use the latest LTS version of Node (check that your local version matches with the one used in the Dockerfile).
The API will run inside a docker container, **but** the install itself is made outside the container, because during development we need tools that need to be available locally (eg. ESLint, Prettier...).
A RabbitMQ instance is also required to send / receive messages when data has been inserted/updated/deleted.
## Installation
Copy `.env.dist` to `.env` :
- copy `.env.dist` to `.env` :
```bash
cp .env.dist .env
```
```bash
cp .env.dist .env
```
and modify it to suit your needs.
Modify it if needed.
Then execute :
- start the containers :
```bash
docker compose up -d
```
```bash
docker compose up -d
```
The app runs automatically on the port defined in `SERVICE_PORT` of `.env` file (default : _5001_).
The app runs automatically on the port defined in `SERVICE_PORT` of `.env` file (default : _5001_).
- install the dependencies :
```bash
npm install
```
## Database migration
@ -96,17 +106,30 @@ As mentionned earlier, RabbitMQ messages are sent after these events :
Various messages are also sent for logging purpose.
## Test
## Tests
Tests are run outside the container for ease of use (switching between different environments inside containers using prisma is complicated and error prone).
The integration tests use a dedicated database (see *db-test* section of *docker-compose.yml*).
```bash
# unit tests
docker exec v3-user sh -c "npm run test"
# test coverage
docker exec v3-user sh -c "npm run test:cov"
# run all tests (unit + integration)
npm run test
```
Note : you can run all npm commands directly _outside_ the container (see _scripts_ section of `package.json` for available commands), but you need NodeJS installed locally. We **strongly** advise to install [Node Version Manager](https://github.com/nvm-sh/nvm) and use the latest LTS version of Node.
```bash
# unit tests only
npm run test:unit
```
```bash
# integration tests only
npm run test:integration
```
```bash
# coverage
npm run test:cov
```
## License

View File

@ -2,7 +2,7 @@ version: '3.8'
services:
api:
container_name: ${SERVICE_CONTAINER}
container_name: v3-user
build:
dockerfile: Dockerfile
context: .
@ -13,7 +13,7 @@ services:
- .env
command: npm run start:dev
ports:
- "${SERVICE_PORT:-5001}:${SERVICE_PORT:-5001}"
- 5001:5001
depends_on:
- db
networks:
@ -22,14 +22,14 @@ services:
- v3-user-api
db:
container_name: ${POSTGRES_CONTAINER}
container_name: v3-user-db
image: ${POSTGRES_IMAGE}
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: user
POSTGRES_USER: user
POSTGRES_PASSWORD: user
ports:
- "${POSTGRES_PORT:-5501}:5432"
- 5501:5432
volumes:
- .postgresql:/var/lib/postgresql/data:rw
networks:
@ -37,6 +37,20 @@ services:
aliases:
- v3-user-db
db-test:
container_name: v3-user-db-test
image: ${POSTGRES_IMAGE}
environment:
POSTGRES_DB: user
POSTGRES_USER: user
POSTGRES_PASSWORD: user
ports:
- 5601:5432
networks:
v3-network:
aliases:
- v3-user-db-test
networks:
v3-network:
name: v3-network

40
package-lock.json generated
View File

@ -38,6 +38,7 @@
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"dotenv-cli": "^6.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
@ -4075,6 +4076,21 @@
"node": ">=12"
}
},
"node_modules/dotenv-cli": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
"integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"dotenv": "^16.0.0",
"dotenv-expand": "^8.0.1",
"minimist": "^1.2.5"
},
"bin": {
"dotenv": "cli.js"
}
},
"node_modules/dotenv-expand": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
@ -6374,9 +6390,9 @@
"dev": true
},
"node_modules/json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true,
"bin": {
"json5": "lib/cli.js"
@ -12115,6 +12131,18 @@
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
"integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ=="
},
"dotenv-cli": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-6.0.0.tgz",
"integrity": "sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==",
"dev": true,
"requires": {
"cross-spawn": "^7.0.3",
"dotenv": "^16.0.0",
"dotenv-expand": "^8.0.1",
"minimist": "^1.2.5"
}
},
"dotenv-expand": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz",
@ -13844,9 +13872,9 @@
"dev": true
},
"json5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"dev": true
},
"jsonc-parser": {

View File

@ -14,11 +14,12 @@
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
"test": "npm run migrate-test && dotenv -e .env.test jest",
"test:unit": "jest --testPathIgnorePatterns 'integration' --verbose",
"test:integration": "npm run migrate-test && dotenv -e .env.test -- jest --testPathPattern 'integration' --verbose",
"test:cov": "npm run migrate-test && dotenv -e .env.test -- jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json",
"migrate-test": "dotenv -e .env.test -- npx prisma migrate dev --name postgres-init"
},
"dependencies": {
"@automapper/classes": "^8.7.7",
@ -33,23 +34,11 @@
"@nestjs/cqrs": "^9.0.1",
"@nestjs/microservices": "^9.2.1",
"@nestjs/platform-express": "^9.0.0",
"@prisma/client": "^4.7.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"dotenv-cli": "^6.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",

View File

@ -0,0 +1,166 @@
import { TestingModule, Test } from '@nestjs/testing';
import { DatabaseModule } from '../../../database/database.module';
import { PrismaService } from '../../../database/src/adapters/secondaries/prisma-service';
import { DatabaseException } from '../../../database/src/exceptions/DatabaseException';
import { UsersRepository } from '../../adapters/secondaries/users.repository';
import { User } from '../../domain/entities/user';
describe('UsersRepository', () => {
let prismaService: PrismaService;
let usersRepository: UsersRepository;
const createUsers = async (nbToCreate = 10) => {
for (let i = 0; i < nbToCreate; i++) {
await prismaService.user.create({
data: {
firstName: `firstName-${i}`,
},
});
}
};
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [DatabaseModule],
providers: [UsersRepository, PrismaService],
}).compile();
prismaService = module.get<PrismaService>(PrismaService);
usersRepository = module.get<UsersRepository>(UsersRepository);
});
afterAll(async () => {
await prismaService.$disconnect();
});
beforeEach(async () => {
await prismaService.user.deleteMany();
});
describe('findAll', () => {
it('should return an empty data array', async () => {
const res = await usersRepository.findAll();
expect(res).toEqual({
data: [],
total: 0,
});
});
it('should return an data array with users', async () => {
await createUsers(10);
const users = await usersRepository.findAll();
expect(users.data.length).toBe(10);
});
});
describe('findOneByUuid', () => {
it('should return a user', async () => {
const userToFind = await prismaService.user.create({
data: {
firstName: 'test',
},
});
const user = await usersRepository.findOneByUuid(userToFind.uuid);
expect(user.uuid).toBe(userToFind.uuid);
});
it('should return null', async () => {
const user = await usersRepository.findOneByUuid(
'544572be-11fb-4244-8235-587221fc9104',
);
expect(user).toBeNull();
});
});
describe('findOne', () => {
it('should return a user according to its email', async () => {
const userToFind = await prismaService.user.create({
data: {
email: 'test@test.com',
},
});
const user = await usersRepository.findOne({
email: 'test@test.com',
});
expect(user.uuid).toBe(userToFind.uuid);
});
it('should return null with unknown email', async () => {
const user = await usersRepository.findOne({
email: 'wrong@email.com',
});
expect(user).toBeNull();
});
});
describe('create', () => {
it('should create a user', async () => {
const beforeCount = await prismaService.user.count();
const userToCreate: User = new User();
userToCreate.firstName = 'test';
const user = await usersRepository.create(userToCreate);
const afterCount = await prismaService.user.count();
expect(afterCount - beforeCount).toBe(1);
expect(user.uuid).toBeDefined();
});
});
describe('update', () => {
it('should update user firstName', async () => {
const userToUpdate = await prismaService.user.create({
data: {
firstName: 'test',
},
});
const toUpdate: User = new User();
toUpdate.firstName = 'updated';
const updateduser = await usersRepository.update(
userToUpdate.uuid,
toUpdate,
);
expect(updateduser.uuid).toBe(userToUpdate.uuid);
expect(updateduser.firstName).toBe('updated');
});
it('should throw DatabaseException', async () => {
const toUpdate: User = new User();
toUpdate.firstName = 'updated';
await expect(
usersRepository.update(
'544572be-11fb-4244-8235-587221fc9104',
toUpdate,
),
).rejects.toBeInstanceOf(DatabaseException);
});
});
describe('delete', () => {
it('should delete a user', async () => {
const userToRemove = await prismaService.user.create({
data: {
firstName: 'test',
},
});
await usersRepository.delete(userToRemove.uuid);
const count = await prismaService.user.count();
expect(count).toBe(0);
});
it('should throw DatabaseException', async () => {
await expect(
usersRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
).rejects.toBeInstanceOf(DatabaseException);
});
});
});