diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..86b2c7c --- /dev/null +++ b/.env.test @@ -0,0 +1,7 @@ +# SERVICE +SERVICE_URL=0.0.0.0 +SERVICE_PORT=5005 +SERVICE_CONFIGURATION_DOMAIN=MATCHER + +# PRISMA +DATABASE_URL="postgresql://mobicoop:mobicoop@localhost:5432/mobicoop-test?schema=matcher" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2086694..0c3717f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,6 +3,7 @@ generator client { provider = "prisma-client-js" + binaryTargets = ["linux-musl", "debian-openssl-3.0.x"] previewFeatures = ["postgresqlExtensions"] } diff --git a/src/modules/ad/adapters/primaries/ad-messager.controller.ts b/src/modules/ad/adapters/primaries/ad-messager.controller.ts index 9774222..ce10434 100644 --- a/src/modules/ad/adapters/primaries/ad-messager.controller.ts +++ b/src/modules/ad/adapters/primaries/ad-messager.controller.ts @@ -1,6 +1,5 @@ import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq'; import { Controller } from '@nestjs/common'; -import { Ad } from '../../domain/entities/ad'; import { CommandBus } from '@nestjs/cqrs'; import { CreateAdCommand } from '../../commands/create-ad.command'; import { CreateAdRequest } from '../../domain/dtos/create-ad.request'; @@ -35,12 +34,8 @@ export class AdMessagerController { throw e; } } - const ad: Ad = await this.commandBus.execute( - new CreateAdCommand(createAdRequest), - ); - console.log(ad); + await this.commandBus.execute(new CreateAdCommand(createAdRequest)); } catch (e) { - console.log(e); this.messager.publish( 'logging.matcher.ad.crit', JSON.stringify({ diff --git a/src/modules/ad/adapters/primaries/ad.presenter.ts b/src/modules/ad/adapters/primaries/ad.presenter.ts deleted file mode 100644 index 1a4d67c..0000000 --- a/src/modules/ad/adapters/primaries/ad.presenter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AutoMap } from '@automapper/classes'; - -export class AdPresenter { - @AutoMap() - uuid: string; -} diff --git a/src/modules/ad/adapters/secondaries/ad.repository.ts b/src/modules/ad/adapters/secondaries/ad.repository.ts index 0c83052..ea580c2 100644 --- a/src/modules/ad/adapters/secondaries/ad.repository.ts +++ b/src/modules/ad/adapters/secondaries/ad.repository.ts @@ -28,28 +28,46 @@ export class AdRepository extends MatcherRepository { driver: ad.driver ? 'true' : 'false', passenger: ad.passenger ? 'true' : 'false', frequency: `'${ad.frequency}'`, - fromDate: `'${ad.fromDate.getFullYear()}-${ad.fromDate.getMonth()}-${ad.fromDate.getDate()}'`, - toDate: `'${ad.toDate.getFullYear()}-${ad.toDate.getMonth()}-${ad.toDate.getDate()}'`, + fromDate: `'${ad.fromDate.getFullYear()}-${ + ad.fromDate.getMonth() + 1 + }-${ad.fromDate.getDate()}'`, + toDate: `'${ad.toDate.getFullYear()}-${ + ad.toDate.getMonth() + 1 + }-${ad.toDate.getDate()}'`, monTime: ad.monTime - ? `'${ad.monTime.getFullYear()}-${ad.monTime.getMonth()}-${ad.monTime.getDate()}T${ad.monTime.getHours()}:${ad.monTime.getMinutes()}Z'` + ? `'${ad.monTime.getFullYear()}-${ + ad.monTime.getMonth() + 1 + }-${ad.monTime.getDate()}T${ad.monTime.getHours()}:${ad.monTime.getMinutes()}Z'` : 'NULL', tueTime: ad.tueTime - ? `'${ad.tueTime.getFullYear()}-${ad.tueTime.getMonth()}-${ad.tueTime.getDate()}T${ad.tueTime.getHours()}:${ad.tueTime.getMinutes()}Z'` + ? `'${ad.tueTime.getFullYear()}-${ + ad.tueTime.getMonth() + 1 + }-${ad.tueTime.getDate()}T${ad.tueTime.getHours()}:${ad.tueTime.getMinutes()}Z'` : 'NULL', wedTime: ad.wedTime - ? `'${ad.wedTime.getFullYear()}-${ad.wedTime.getMonth()}-${ad.wedTime.getDate()}T${ad.wedTime.getHours()}:${ad.wedTime.getMinutes()}Z'` + ? `'${ad.wedTime.getFullYear()}-${ + ad.wedTime.getMonth() + 1 + }-${ad.wedTime.getDate()}T${ad.wedTime.getHours()}:${ad.wedTime.getMinutes()}Z'` : 'NULL', thuTime: ad.thuTime - ? `'${ad.thuTime.getFullYear()}-${ad.thuTime.getMonth()}-${ad.thuTime.getDate()}T${ad.thuTime.getHours()}:${ad.thuTime.getMinutes()}Z'` + ? `'${ad.thuTime.getFullYear()}-${ + ad.thuTime.getMonth() + 1 + }-${ad.thuTime.getDate()}T${ad.thuTime.getHours()}:${ad.thuTime.getMinutes()}Z'` : 'NULL', friTime: ad.friTime - ? `'${ad.friTime.getFullYear()}-${ad.friTime.getMonth()}-${ad.friTime.getDate()}T${ad.friTime.getHours()}:${ad.friTime.getMinutes()}Z'` + ? `'${ad.friTime.getFullYear()}-${ + ad.friTime.getMonth() + 1 + }-${ad.friTime.getDate()}T${ad.friTime.getHours()}:${ad.friTime.getMinutes()}Z'` : 'NULL', satTime: ad.satTime - ? `'${ad.satTime.getFullYear()}-${ad.satTime.getMonth()}-${ad.satTime.getDate()}T${ad.satTime.getHours()}:${ad.satTime.getMinutes()}Z'` + ? `'${ad.satTime.getFullYear()}-${ + ad.satTime.getMonth() + 1 + }-${ad.satTime.getDate()}T${ad.satTime.getHours()}:${ad.satTime.getMinutes()}Z'` : 'NULL', sunTime: ad.sunTime - ? `'${ad.sunTime.getFullYear()}-${ad.sunTime.getMonth()}-${ad.sunTime.getDate()}T${ad.sunTime.getHours()}:${ad.sunTime.getMinutes()}Z'` + ? `'${ad.sunTime.getFullYear()}-${ + ad.sunTime.getMonth() + 1 + }-${ad.sunTime.getDate()}T${ad.sunTime.getHours()}:${ad.sunTime.getMinutes()}Z'` : 'NULL', monMargin: ad.monMargin, tueMargin: ad.tueMargin, diff --git a/src/modules/ad/domain/usecases/create-ad.usecase.ts b/src/modules/ad/domain/usecases/create-ad.usecase.ts index b04b2f3..71fcc98 100644 --- a/src/modules/ad/domain/usecases/create-ad.usecase.ts +++ b/src/modules/ad/domain/usecases/create-ad.usecase.ts @@ -15,7 +15,7 @@ import { Role } from '../types/role.enum'; import { Geography } from '../entities/geography'; import { IEncodeDirection } from '../../../geography/domain/interfaces/direction-encoder.interface'; import { TimeConverter } from '../entities/time-converter'; -import { Coordinate } from 'src/modules/geography/domain/entities/coordinate'; +import { Coordinate } from '../../../geography/domain/entities/coordinate'; @CommandHandler(CreateAdCommand) export class CreateAdUseCase { @@ -68,8 +68,8 @@ export class CreateAdUseCase { this.timezone = this.defaultParams.DEFAULT_TIMEZONE; try { const timezones = this.timezoneFinder.timezones( - coordinates[0].lat, coordinates[0].lon, + coordinates[0].lat, ); if (timezones.length > 0) this.timezone = timezones[0]; } catch (e) {} diff --git a/src/modules/ad/mappers/ad.profile.ts b/src/modules/ad/mappers/ad.profile.ts index 7b7de92..bbc38d5 100644 --- a/src/modules/ad/mappers/ad.profile.ts +++ b/src/modules/ad/mappers/ad.profile.ts @@ -2,7 +2,6 @@ import { createMap, Mapper } from '@automapper/core'; import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; import { Injectable } from '@nestjs/common'; import { Ad } from '../domain/entities/ad'; -import { AdPresenter } from '../adapters/primaries/ad.presenter'; import { CreateAdRequest } from '../domain/dtos/create-ad.request'; @Injectable() @@ -13,7 +12,6 @@ export class AdProfile extends AutomapperProfile { override get profile() { return (mapper: any) => { - createMap(mapper, Ad, AdPresenter); createMap(mapper, CreateAdRequest, Ad); }; } diff --git a/src/modules/ad/tests/integration/ad.repository.spec.ts b/src/modules/ad/tests/integration/ad.repository.spec.ts new file mode 100644 index 0000000..1947bac --- /dev/null +++ b/src/modules/ad/tests/integration/ad.repository.spec.ts @@ -0,0 +1,402 @@ +import { TestingModule, Test } from '@nestjs/testing'; +import { DatabaseModule } from '../../../database/database.module'; +import { PrismaService } from '../../../database/adapters/secondaries/prisma-service'; +import { AdRepository } from '../../adapters/secondaries/ad.repository'; +import { Ad } from '../../domain/entities/ad'; +import { Frequency } from '../../domain/types/frequency.enum'; + +describe('AdRepository', () => { + let prismaService: PrismaService; + let adRepository: AdRepository; + + const baseUuid = { + uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00', + }; + const baseUserUuid = { + userUuid: '4e52b54d-a729-4dbd-9283-f84a11bb2200', + }; + const driverAd = { + driver: 'true', + passenger: 'false', + fwdAzimuth: 0, + backAzimuth: 180, + waypoints: "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'", + direction: "'LINESTRING(6 47,6.05 47.05,6.1 47.1,6.15 47.15,6.2 47.2)'", + seatsDriver: 3, + seatsPassenger: 1, + seatsUsed: 0, + strict: 'false', + }; + const passengerAd = { + driver: 'false', + passenger: 'true', + fwdAzimuth: 0, + backAzimuth: 180, + waypoints: "'LINESTRING(6 47,6.2 47.2)'", + direction: "'LINESTRING(6 47,6.05 47.05,6.15 47.15,6.2 47.2)'", + seatsDriver: 3, + seatsPassenger: 1, + seatsUsed: 0, + strict: 'false', + }; + const driverAndPassengerAd = { + driver: 'true', + passenger: 'true', + fwdAzimuth: 0, + backAzimuth: 180, + waypoints: "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'", + direction: "'LINESTRING(6 47,6.05 47.05,6.1 47.1,6.15 47.15,6.2 47.2)'", + seatsDriver: 3, + seatsPassenger: 1, + seatsUsed: 0, + strict: 'false', + }; + const punctualAd = { + frequency: `'PUNCTUAL'`, + fromDate: `'2023-01-01'`, + toDate: `'2023-01-01'`, + monTime: 'NULL', + tueTime: 'NULL', + wedTime: 'NULL', + thuTime: 'NULL', + friTime: 'NULL', + satTime: 'NULL', + sunTime: `'2023-01-01T07:00Z'`, + monMargin: 900, + tueMargin: 900, + wedMargin: 900, + thuMargin: 900, + friMargin: 900, + satMargin: 900, + sunMargin: 900, + }; + const recurrentAd = { + frequency: `'RECURRENT'`, + fromDate: `'2023-01-01'`, + toDate: `'2023-12-31'`, + monTime: `'2023-01-01T07:00Z'`, + tueTime: `'2023-01-01T07:00Z'`, + wedTime: `'2023-01-01T07:00Z'`, + thuTime: `'2023-01-01T07:00Z'`, + friTime: `'2023-01-01T07:00Z'`, + satTime: 'NULL', + sunTime: 'NULL', + monMargin: 900, + tueMargin: 900, + wedMargin: 900, + thuMargin: 900, + friMargin: 900, + satMargin: 900, + sunMargin: 900, + }; + + const createPunctualDriverAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...driverAd, + ...punctualAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const createRecurrentDriverAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...driverAd, + ...recurrentAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const createPunctualPassengerAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...passengerAd, + ...punctualAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const createRecurrentPassengerAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...passengerAd, + ...recurrentAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const createPunctualDriverPassengerAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...driverAndPassengerAd, + ...punctualAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const createRecurrentDriverPassengerAds = async (nbToCreate = 10) => { + const adToCreate = { + ...baseUuid, + ...baseUserUuid, + ...driverAndPassengerAd, + ...recurrentAd, + }; + for (let i = 0; i < nbToCreate; i++) { + adToCreate.uuid = `'${baseUuid.uuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + adToCreate.userUuid = `'${baseUserUuid.userUuid.slice(0, -2)}${i + .toString(16) + .padStart(2, '0')}'`; + await executeInsertCommand(adToCreate); + } + }; + + const executeInsertCommand = async (object: any) => { + const command = `INSERT INTO ad ("${Object.keys(object).join( + '","', + )}") VALUES (${Object.values(object).join(',')})`; + await prismaService.$executeRawUnsafe(command); + }; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [DatabaseModule], + providers: [AdRepository, PrismaService], + }).compile(); + + prismaService = module.get(PrismaService); + adRepository = module.get(AdRepository); + }); + + afterAll(async () => { + await prismaService.$disconnect(); + }); + + beforeEach(async () => { + await prismaService.ad.deleteMany(); + }); + + describe('findAll', () => { + it('should return an empty data array', async () => { + const res = await adRepository.findAll(); + expect(res).toEqual({ + data: [], + total: 0, + }); + }); + + describe('drivers', () => { + it('should return a data array with 8 punctual driver ads', async () => { + await createPunctualDriverAds(8); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(8); + expect(ads.total).toBe(8); + expect(ads.data[0].driver).toBeTruthy(); + expect(ads.data[0].passenger).toBeFalsy(); + }); + + it('should return a data array limited to 10 punctual driver ads', async () => { + await createPunctualDriverAds(20); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(20); + expect(ads.data[1].driver).toBeTruthy(); + expect(ads.data[1].passenger).toBeFalsy(); + }); + + it('should return a data array with 8 recurrent driver ads', async () => { + await createRecurrentDriverAds(8); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(8); + expect(ads.total).toBe(8); + expect(ads.data[2].driver).toBeTruthy(); + expect(ads.data[2].passenger).toBeFalsy(); + }); + + it('should return a data array limited to 10 recurrent driver ads', async () => { + await createRecurrentDriverAds(20); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(20); + expect(ads.data[3].driver).toBeTruthy(); + expect(ads.data[3].passenger).toBeFalsy(); + }); + }); + + describe('passengers', () => { + it('should return a data array with 7 punctual passenger ads', async () => { + await createPunctualPassengerAds(7); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(7); + expect(ads.total).toBe(7); + expect(ads.data[0].passenger).toBeTruthy(); + expect(ads.data[0].driver).toBeFalsy(); + }); + + it('should return a data array limited to 10 punctual passenger ads', async () => { + await createPunctualPassengerAds(15); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(15); + expect(ads.data[1].passenger).toBeTruthy(); + expect(ads.data[1].driver).toBeFalsy(); + }); + + it('should return a data array with 7 recurrent passenger ads', async () => { + await createRecurrentPassengerAds(7); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(7); + expect(ads.total).toBe(7); + expect(ads.data[2].passenger).toBeTruthy(); + expect(ads.data[2].driver).toBeFalsy(); + }); + + it('should return a data array limited to 10 recurrent passenger ads', async () => { + await createRecurrentPassengerAds(15); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(15); + expect(ads.data[3].passenger).toBeTruthy(); + expect(ads.data[3].driver).toBeFalsy(); + }); + }); + + describe('drivers and passengers', () => { + it('should return a data array with 6 punctual driver and passenger ads', async () => { + await createPunctualDriverPassengerAds(6); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(6); + expect(ads.total).toBe(6); + expect(ads.data[0].passenger).toBeTruthy(); + expect(ads.data[0].driver).toBeTruthy(); + }); + + it('should return a data array limited to 10 punctual driver and passenger ads', async () => { + await createPunctualDriverPassengerAds(16); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(16); + expect(ads.data[1].passenger).toBeTruthy(); + expect(ads.data[1].driver).toBeTruthy(); + }); + + it('should return a data array with 6 recurrent driver and passenger ads', async () => { + await createRecurrentDriverPassengerAds(6); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(6); + expect(ads.total).toBe(6); + expect(ads.data[2].passenger).toBeTruthy(); + expect(ads.data[2].driver).toBeTruthy(); + }); + + it('should return a data array limited to 10 recurrent driver and passenger ads', async () => { + await createRecurrentDriverPassengerAds(16); + const ads = await adRepository.findAll(); + expect(ads.data.length).toBe(10); + expect(ads.total).toBe(16); + expect(ads.data[3].passenger).toBeTruthy(); + expect(ads.data[3].driver).toBeTruthy(); + }); + }); + }); + + describe('findOneByUuid', () => { + it('should return an ad', async () => { + await createPunctualDriverAds(1); + const ad = await adRepository.findOneByUuid(baseUuid.uuid); + expect(ad.uuid).toBe(baseUuid.uuid); + }); + + it('should return null', async () => { + const ad = await adRepository.findOneByUuid( + '544572be-11fb-4244-8235-587221fc9104', + ); + expect(ad).toBeNull(); + }); + }); + + describe('create', () => { + it('should create an ad', async () => { + const beforeCount = await prismaService.ad.count(); + + const adToCreate: Ad = new Ad(); + adToCreate.uuid = 'be459a29-7a41-4c0b-b371-abe90bfb6f00'; + adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200'; + adToCreate.driver = true; + adToCreate.passenger = false; + adToCreate.fwdAzimuth = 0; + adToCreate.backAzimuth = 180; + adToCreate.waypoints = "'LINESTRING(6 47,6.1 47.1,6.2 47.2)'"; + adToCreate.direction = + "'LINESTRING(6 47,6.05 47.05,6.1 47.1,6.15 47.15,6.2 47.2)'"; + adToCreate.seatsDriver = 3; + adToCreate.seatsPassenger = 1; + adToCreate.seatsUsed = 0; + adToCreate.strict = false; + adToCreate.frequency = Frequency.PUNCTUAL; + adToCreate.fromDate = new Date(2023, 0, 1); + adToCreate.toDate = new Date(2023, 0, 1); + adToCreate.sunTime = new Date(2023, 0, 1, 6, 0, 0); + adToCreate.monMargin = 900; + adToCreate.tueMargin = 900; + adToCreate.wedMargin = 900; + adToCreate.thuMargin = 900; + adToCreate.friMargin = 900; + adToCreate.satMargin = 900; + adToCreate.sunMargin = 900; + const ad = await adRepository.createAd(adToCreate); + + const afterCount = await prismaService.ad.count(); + + expect(afterCount - beforeCount).toBe(1); + expect(ad.uuid).toBe('be459a29-7a41-4c0b-b371-abe90bfb6f00'); + }); + }); +});