integration tests

This commit is contained in:
Gsk54 2023-01-06 14:37:26 +01:00
parent f2f08eb5c1
commit bb82488e0d
12 changed files with 534 additions and 36 deletions

View File

@ -1,5 +1,4 @@
# SERVICE
SERVICE_CONTAINER=v3-auth
SERVICE_URL=0.0.0.0
SERVICE_PORT=5002
@ -7,12 +6,7 @@ SERVICE_PORT=5002
DATABASE_URL="postgresql://auth:auth@db:5432/auth?schema=public"
# RABBIT MQ
RMQ_URI=amqp://localhost:5672
RMQ_URI=amqp://v3-broker:5672
# POSTGRES
POSTGRES_CONTAINER=v3-auth-db
POSTGRES_IMAGE=postgres:15.0
POSTGRES_DB=auth
POSTGRES_PASSWORD=auth
POSTGRES_USER=auth
POSTGRES_PORT=5502

12
.env.test Normal file
View File

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

View File

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

56
package-lock.json generated
View File

@ -38,8 +38,10 @@
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@types/uuid": "^9.0.0",
"@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",
@ -52,7 +54,8 @@
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"uuid": "^9.0.0"
}
},
"node_modules/@ampproject/remapping": {
@ -2506,6 +2509,12 @@
"@types/superagent": "*"
}
},
"node_modules/@types/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
"dev": true
},
"node_modules/@types/validator": {
"version": "13.7.10",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
@ -4110,6 +4119,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",
@ -6467,9 +6491,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"
@ -11120,6 +11144,12 @@
"@types/superagent": "*"
}
},
"@types/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
"dev": true
},
"@types/validator": {
"version": "13.7.10",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
@ -12322,6 +12352,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",
@ -14099,9 +14141,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",
@ -50,8 +51,10 @@
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@types/uuid": "^9.0.0",
"@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",
@ -64,7 +67,8 @@
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
"typescript": "^4.7.4",
"uuid": "^9.0.0"
},
"jest": {
"moduleFileExtensions": [

View File

@ -17,9 +17,7 @@ export class DeleteAuthUseCase {
await this._usernameRepository.deleteMany({
uuid: command.deleteAuthRequest.uuid,
});
return await this._authRepository.delete({
uuid: command.deleteAuthRequest.uuid,
});
return await this._authRepository.delete(command.deleteAuthRequest.uuid);
} catch (error) {
this._loggingMessager.publish(
'auth.delete.crit',

View File

@ -21,7 +21,7 @@ export class DeleteUsernameUseCase {
uuid: usernameFound.uuid,
});
if (usernames.total > 1) {
return await this._usernameRepository.delete({ username });
return await this._usernameRepository.deleteMany({ username });
}
throw new UnauthorizedException();
} catch (error) {

View File

@ -0,0 +1,153 @@
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 { AuthRepository } from '../../adapters/secondaries/auth.repository';
import { v4 } from 'uuid';
import * as bcrypt from 'bcrypt';
import { Auth } from '../../domain/entities/auth';
describe('AuthRepository', () => {
let prismaService: PrismaService;
let authRepository: AuthRepository;
const createAuths = async (nbToCreate = 10) => {
for (let i = 0; i < nbToCreate; i++) {
await prismaService.auth.create({
data: {
uuid: v4(),
password: bcrypt.hashSync(`password-${i}`, 10),
},
});
}
};
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [DatabaseModule],
providers: [AuthRepository, PrismaService],
}).compile();
prismaService = module.get<PrismaService>(PrismaService);
authRepository = module.get<AuthRepository>(AuthRepository);
});
afterAll(async () => {
await prismaService.$disconnect();
});
beforeEach(async () => {
await prismaService.auth.deleteMany();
});
describe('findAll', () => {
it('should return an empty data array', async () => {
const res = await authRepository.findAll();
expect(res).toEqual({
data: [],
total: 0,
});
});
it('should return a data array with 8 auths', async () => {
await createAuths(8);
const auths = await authRepository.findAll();
expect(auths.data.length).toBe(8);
expect(auths.total).toBe(8);
});
it('should return a data array limited to 10 auths', async () => {
await createAuths(20);
const auths = await authRepository.findAll();
expect(auths.data.length).toBe(10);
expect(auths.total).toBe(20);
});
});
describe('findOneByUuid', () => {
it('should return an auth', async () => {
const authToFind = await prismaService.auth.create({
data: {
uuid: v4(),
password: bcrypt.hashSync(`password`, 10),
},
});
const auth = await authRepository.findOneByUuid(authToFind.uuid);
expect(auth.uuid).toBe(authToFind.uuid);
});
it('should return null', async () => {
const auth = await authRepository.findOneByUuid(
'544572be-11fb-4244-8235-587221fc9104',
);
expect(auth).toBeNull();
});
});
describe('create', () => {
it('should create an auth', async () => {
const beforeCount = await prismaService.auth.count();
const authToCreate: Auth = new Auth();
authToCreate.uuid = v4();
authToCreate.password = bcrypt.hashSync(`password`, 10);
const auth = await authRepository.create(authToCreate);
const afterCount = await prismaService.auth.count();
expect(afterCount - beforeCount).toBe(1);
expect(auth.uuid).toBeDefined();
});
});
describe('update', () => {
it('should update auth password', async () => {
const authToUpdate = await prismaService.auth.create({
data: {
uuid: v4(),
password: bcrypt.hashSync(`password`, 10),
},
});
const toUpdate: Auth = new Auth();
toUpdate.password = bcrypt.hashSync(`newPassword`, 10);
const updatedAuth = await authRepository.update(
authToUpdate.uuid,
toUpdate,
);
expect(updatedAuth.uuid).toBe(authToUpdate.uuid);
});
it('should throw DatabaseException', async () => {
const toUpdate: Auth = new Auth();
toUpdate.password = bcrypt.hashSync(`newPassword`, 10);
await expect(
authRepository.update('544572be-11fb-4244-8235-587221fc9104', toUpdate),
).rejects.toBeInstanceOf(DatabaseException);
});
});
describe('delete', () => {
it('should delete an auth', async () => {
const authToRemove = await prismaService.auth.create({
data: {
uuid: v4(),
password: bcrypt.hashSync(`password`, 10),
},
});
await authRepository.delete(authToRemove.uuid);
const count = await prismaService.auth.count();
expect(count).toBe(0);
});
it('should throw DatabaseException', async () => {
await expect(
authRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
).rejects.toBeInstanceOf(DatabaseException);
});
});
});

View File

@ -0,0 +1,282 @@
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 { v4 } from 'uuid';
import { Type } from '../../domain/dtos/type.enum';
import { UsernameRepository } from '../../adapters/secondaries/username.repository';
import { Username } from '../../domain/entities/username';
describe('UsernameRepository', () => {
let prismaService: PrismaService;
let usernameRepository: UsernameRepository;
const createUsernames = async (nbToCreate = 10) => {
for (let i = 0; i < nbToCreate; i++) {
await prismaService.username.create({
data: {
uuid: v4(),
username: `john.doe.${i}@email.com`,
type: Type.EMAIL,
},
});
}
};
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [DatabaseModule],
providers: [UsernameRepository, PrismaService],
}).compile();
prismaService = module.get<PrismaService>(PrismaService);
usernameRepository = module.get<UsernameRepository>(UsernameRepository);
});
afterAll(async () => {
await prismaService.$disconnect();
});
beforeEach(async () => {
await prismaService.username.deleteMany();
});
describe('findAll', () => {
it('should return an empty data array', async () => {
const res = await usernameRepository.findAll();
expect(res).toEqual({
data: [],
total: 0,
});
});
it('should return a data array with 8 usernames', async () => {
await createUsernames(8);
const usernames = await usernameRepository.findAll();
expect(usernames.data.length).toBe(8);
expect(usernames.total).toBe(8);
});
it('should return a data array limited to 10 usernames', async () => {
await createUsernames(20);
const usernames = await usernameRepository.findAll();
expect(usernames.data.length).toBe(10);
expect(usernames.total).toBe(20);
});
});
describe('findOne', () => {
it('should return a username with uuid and email', async () => {
const usernameToFind = await prismaService.username.create({
data: {
uuid: v4(),
username: 'john.doe@email.com',
type: Type.EMAIL,
},
});
const username = await usernameRepository.findOne({
username: 'john.doe@email.com',
type: Type.EMAIL,
});
expect(username.uuid).toBe(usernameToFind.uuid);
});
it('should return null', async () => {
const username = await usernameRepository.findOne({
username: 'jane.doe@email.com',
type: Type.EMAIL,
});
expect(username).toBeNull();
});
});
describe('create', () => {
it('should create a username with an email', async () => {
const beforeCount = await prismaService.username.count();
const usernameToCreate: Username = new Username();
usernameToCreate.uuid = v4();
usernameToCreate.username = 'john.doe@email.com';
usernameToCreate.type = Type.EMAIL;
const username = await usernameRepository.create(usernameToCreate);
const afterCount = await prismaService.username.count();
expect(afterCount - beforeCount).toBe(1);
expect(username.uuid).toBeDefined();
});
it('should create a username with a phone number', async () => {
const beforeCount = await prismaService.username.count();
const usernameToCreate: Username = new Username();
usernameToCreate.uuid = v4();
usernameToCreate.username = '+33611223344';
usernameToCreate.type = Type.PHONE;
const username = await usernameRepository.create(usernameToCreate);
const afterCount = await prismaService.username.count();
expect(afterCount - beforeCount).toBe(1);
expect(username.uuid).toBeDefined();
});
it('should create a username with an email for an existing uuid', async () => {
const beforeCount = await prismaService.username.count();
const uuid = v4();
const firstUsernameToCreate: Username = new Username();
firstUsernameToCreate.uuid = uuid;
firstUsernameToCreate.username = '+33611223344';
firstUsernameToCreate.type = Type.PHONE;
const firstUsername = await usernameRepository.create(
firstUsernameToCreate,
);
const secondUsernameToCreate: Username = new Username();
secondUsernameToCreate.uuid = uuid;
secondUsernameToCreate.username = 'john.doe@email.com';
secondUsernameToCreate.type = Type.EMAIL;
const secondUsername = await usernameRepository.create(
secondUsernameToCreate,
);
const afterCount = await prismaService.username.count();
expect(afterCount - beforeCount).toBe(2);
expect(firstUsername.uuid).toEqual(secondUsername.uuid);
});
it('should throw DatabaseException if username already exists for a given type', async () => {
const uuid = v4();
const firstUsernameToCreate: Username = new Username();
firstUsernameToCreate.uuid = uuid;
firstUsernameToCreate.username = 'john.doe@email.com';
firstUsernameToCreate.type = Type.EMAIL;
await usernameRepository.create(firstUsernameToCreate);
const secondUsernameToCreate: Username = new Username();
secondUsernameToCreate.uuid = uuid;
secondUsernameToCreate.username = 'jane.doe@email.com';
secondUsernameToCreate.type = Type.EMAIL;
await expect(
usernameRepository.create(secondUsernameToCreate),
).rejects.toBeInstanceOf(DatabaseException);
});
});
describe('update', () => {
it('should update username email', async () => {
const usernameToUpdate = await prismaService.username.create({
data: {
uuid: v4(),
username: `john.doe@email.com`,
type: Type.EMAIL,
},
});
const toUpdate: Username = new Username();
toUpdate.username = 'jane.doe@email.com';
const updatedUsername = await usernameRepository.updateWhere(
{
uuid_type: {
uuid: usernameToUpdate.uuid,
type: usernameToUpdate.type,
},
},
{
username: toUpdate.username,
},
);
expect(updatedUsername.uuid).toBe(usernameToUpdate.uuid);
expect(updatedUsername.username).toBe('jane.doe@email.com');
});
it('should update username phone', async () => {
const usernameToUpdate = await prismaService.username.create({
data: {
uuid: v4(),
username: `+33611223344`,
type: Type.PHONE,
},
});
const toUpdate: Username = new Username();
toUpdate.username = '+33622334455';
const updatedUsername = await usernameRepository.updateWhere(
{
uuid_type: {
uuid: usernameToUpdate.uuid,
type: usernameToUpdate.type,
},
},
{
username: toUpdate.username,
},
);
expect(updatedUsername.uuid).toBe(usernameToUpdate.uuid);
expect(updatedUsername.username).toBe('+33622334455');
});
it('should throw DatabaseException if email not found', async () => {
const toUpdate: Username = new Username();
toUpdate.username = 'jane.doe@email.com';
await expect(
usernameRepository.updateWhere(
{
uuid_type: {
uuid: '544572be-11fb-4244-8235-587221fc9104',
type: Type.EMAIL,
},
},
{
username: toUpdate.username,
},
),
).rejects.toBeInstanceOf(DatabaseException);
});
it('should throw DatabaseException if phone not found', async () => {
const toUpdate: Username = new Username();
toUpdate.username = '+33611223344';
await expect(
usernameRepository.updateWhere(
{
uuid_type: {
uuid: '544572be-11fb-4244-8235-587221fc9104',
type: Type.PHONE,
},
},
{
username: toUpdate.username,
},
),
).rejects.toBeInstanceOf(DatabaseException);
});
});
describe('delete', () => {
it('should delete a username', async () => {
const usernameToRemove = await prismaService.username.create({
data: {
uuid: v4(),
username: `+33611223344`,
type: Type.PHONE,
},
});
await usernameRepository.deleteMany({ uuid: usernameToRemove.uuid });
const count = await prismaService.username.count();
expect(count).toBe(0);
});
it('should throw DatabaseException', async () => {
await expect(
usernameRepository.delete('544572be-11fb-4244-8235-587221fc9104'),
).rejects.toBeInstanceOf(DatabaseException);
});
});
});

View File

@ -63,7 +63,7 @@ const mockUsernameRepository = {
}
return Promise.resolve(usernamesPhone);
}),
delete: jest.fn().mockResolvedValue(undefined),
deleteMany: jest.fn().mockResolvedValue(undefined),
};
const mockMessager = {

View File

@ -131,7 +131,6 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
return updatedEntity;
} catch (e) {
console.log('error', e);
if (e instanceof PrismaClientKnownRequestError) {
throw new DatabaseException(
PrismaClientKnownRequestError.name,
@ -144,10 +143,10 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
}
}
async delete(where: any): Promise<void> {
async delete(uuid: string): Promise<void> {
try {
const entity = await this._prisma[this._model].delete({
where: where,
where: { uuid },
});
return entity;

View File

@ -231,7 +231,7 @@ describe('PrismaRepository', () => {
const savedUuid = fakeEntities[0].uuid;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const res = await fakeRepository.delete({ uuid: savedUuid });
const res = await fakeRepository.delete(savedUuid);
const deletedEntity = fakeEntities.find(
(entity) => entity.uuid === savedUuid,