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
SERVICE_CONTAINER=v3-user
SERVICE_URL=0.0.0.0 SERVICE_URL=0.0.0.0
SERVICE_PORT=5001
# PRISMA # PRISMA
DATABASE_URL="postgresql://user:user@v3-user-db:5432/user?schema=public" DATABASE_URL="postgresql://user:user@v3-user-db:5432/user?schema=public"
# RABBIT MQ # RABBIT MQ
RMQ_URI=amqp://localhost:5672 RMQ_URI=amqp://v3-broker:5672
# POSTGRES # POSTGRES
POSTGRES_CONTAINER=v3-user-db
POSTGRES_IMAGE=postgres:15.0 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 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. A RabbitMQ instance is also required to send / receive messages when data has been inserted/updated/deleted.
## Installation ## Installation
Copy `.env.dist` to `.env` : - copy `.env.dist` to `.env` :
```bash ```bash
cp .env.dist .env cp .env.dist .env
``` ```
and modify it to suit your needs. Modify it if needed.
Then execute : - start the containers :
```bash ```bash
docker compose up -d 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 ## Database migration
@ -96,17 +106,30 @@ As mentionned earlier, RabbitMQ messages are sent after these events :
Various messages are also sent for logging purpose. 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 ```bash
# unit tests # run all tests (unit + integration)
docker exec v3-user sh -c "npm run test" npm run test
# test coverage
docker exec v3-user sh -c "npm run test:cov"
``` ```
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 ## License

View File

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

40
package-lock.json generated
View File

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

View File

@ -14,11 +14,12 @@
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "npm run migrate-test && dotenv -e .env.test jest",
"test:watch": "jest --watch", "test:unit": "jest --testPathIgnorePatterns 'integration' --verbose",
"test:cov": "jest --coverage", "test:integration": "npm run migrate-test && dotenv -e .env.test -- jest --testPathPattern 'integration' --verbose",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:cov": "npm run migrate-test && dotenv -e .env.test -- jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json",
"migrate-test": "dotenv -e .env.test -- npx prisma migrate dev --name postgres-init"
}, },
"dependencies": { "dependencies": {
"@automapper/classes": "^8.7.7", "@automapper/classes": "^8.7.7",
@ -33,23 +34,11 @@
"@nestjs/cqrs": "^9.0.1", "@nestjs/cqrs": "^9.0.1",
"@nestjs/microservices": "^9.2.1", "@nestjs/microservices": "^9.2.1",
"@nestjs/platform-express": "^9.0.0", "@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/node": "^16.0.0",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.0.0",
"dotenv-cli": "^6.0.0",
"eslint": "^8.0.1", "eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.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);
});
});
});