test find-by-id query

This commit is contained in:
sbriat 2023-06-23 15:35:37 +02:00
parent 0409670eec
commit b7bb656f10
9 changed files with 165 additions and 70 deletions

View File

@ -48,30 +48,34 @@ npm run migrate
The app exposes the following [gRPC](https://grpc.io/) services : The app exposes the following [gRPC](https://grpc.io/) services :
- **FindByUuid** : find an ad by its uuid - **FindById** : find an ad by its id
```json ```json
{ {
"uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1" "id": "80126a61-d128-4f96-afdb-92e33c75a3e1"
} }
``` ```
- **Create** : create an ad (note that uuid is optional, a uuid will be automatically attributed if it is not provided) - **Create** : create an ad (note that id is optional, an id (as a uuid) will be automatically attributed if it is not provided)
Punctual driver ad : Punctual driver ad :
```json ```json
{ {
"userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", "userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
"driver": true, "driver": true,
"seatsDriver": 3, "seatsProposed": 3,
"frequency": "PUNCTUAL", "frequency": "PUNCTUAL",
"departure": "2023-01-15 09:00", "fromDate": "2023-01-15",
"addresses": [ "toDate": "2023-01-15",
"schedule": {
"thu": "09:00"
},
"waypoints": [
{ {
"position": 0, "position": 0,
"lon": 48.68944505415954, "lon": 48.689445,
"lat": 6.176510296462267, "lat": 6.17651,
"houseNumber": "5", "houseNumber": "5",
"street": "Avenue Foch", "street": "Avenue Foch",
"locality": "Nancy", "locality": "Nancy",
@ -94,14 +98,18 @@ The app exposes the following [gRPC](https://grpc.io/) services :
```json ```json
{ {
"userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", "userId": "80c9bb02-0931-4a1d-bea6-22d358992245",
"driver": true, "driver": true,
"pasenger": true, "pasenger": true,
"seatsDriver": 3, "seatsProposed": 3,
"seatsPassenger": 1, "seatsRequested": 1,
"frequency": "PUNCTUAL", "frequency": "PUNCTUAL",
"departure": "2023-01-15 09:00", "fromDate": "2023-01-15",
"addresses": [ "toDate": "2023-01-15",
"schedule": {
"thu": "09:00"
},
"waypoints": [
{ {
"position": 0, "position": 0,
"lon": 48.68944505415954, "lon": 48.68944505415954,
@ -139,11 +147,11 @@ The app exposes the following [gRPC](https://grpc.io/) services :
"tue": "07:05", "tue": "07:05",
"fri": "07:10" "fri": "07:10"
}, },
"addresses": [ "waypoints": [
{ {
"position": 0, "position": 0,
"lon": 48.68944505415954, "lon": 48.689445,
"lat": 6.176510296462267, "lat": 6.17651,
"houseNumber": "5", "houseNumber": "5",
"street": "Avenue Foch", "street": "Avenue Foch",
"locality": "Nancy", "locality": "Nancy",
@ -164,15 +172,14 @@ The app exposes the following [gRPC](https://grpc.io/) services :
The list of possible options when creating an ad : The list of possible options when creating an ad :
- uuid (optional): the uuid of the ad - id (optional): the id of the ad (as a uuid)
- userUuid: the user uuid - userId: the user id (as a uuid)
- driver (boolean, optional): if the ad is a driver ad - driver (boolean, optional): if the ad is a driver ad
- passenger (boolean, optional): if the ad is a passenger ad - passenger (boolean, optional): if the ad is a passenger ad
- frequency: `PUNCTUAL` or `RECURRENT` - frequency: `PUNCTUAL` or `RECURRENT`
- departure (required if punctual): departure date and hour/minute for a punctual ad - fromDate: start date for recurrent ad, carpool date for punctual ad
- fromDate (required if recurrent): start date for recurrent ad - toDate: end date for recurrent ad, same as fromDate for punctual ad
- toDate (required if recurrent): end date for recurrent ad - schedule: an object with the departure time for each carpooled day in the week
- schedule (required if recurrent): an object with the departure time for each carpooled day in the week
- marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg: - marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg:
{ {
@ -181,10 +188,10 @@ The app exposes the following [gRPC](https://grpc.io/) services :
"fri": 950 "fri": 950
} }
- seatsDriver (optional): number of seats proposed as driver; - seatsProposed (optional): number of seats proposed as driver
- seatsPassenger (optional): number of seats requested as passenger; - seatsRequested (optional): number of seats requested as passenger
- strict (boolean, optional): if set to true, allow matching only with similar frequency ads - strict (boolean, optional): if set to true, allow matching only with similar frequency ads
- addresses: an array of adresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads) - waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions **must** be consecutives
Default values must be set in `.env` file. Default values must be set in `.env` file.

View File

@ -2,8 +2,6 @@ import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
// import { HealthModule } from './modules/health/health.module'; // import { HealthModule } from './modules/health/health.module';
import { AdModule } from './modules/ad/ad.module'; import { AdModule } from './modules/ad/ad.module';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
import { import {
MessageBrokerModule, MessageBrokerModule,
MessageBrokerModuleOptions, MessageBrokerModuleOptions,
@ -21,7 +19,6 @@ import { HealthModule } from '@modules/health/health.module';
ConfigModule.forRoot({ isGlobal: true }), ConfigModule.forRoot({ isGlobal: true }),
EventEmitterModule.forRoot(), EventEmitterModule.forRoot(),
RequestContextModule, RequestContextModule,
AutomapperModule.forRoot({ strategyInitializer: classes() }),
MessageBrokerModule.forRootAsync({ MessageBrokerModule.forRootAsync({
imports: [ConfigModule], imports: [ConfigModule],
inject: [ConfigService], inject: [ConfigService],

View File

@ -1,33 +1,26 @@
import { AutoMap } from '@automapper/classes';
import { IsOptional, IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';
import { CoordinatesDto as CoordinatesDto } from './coordinates.dto'; import { CoordinatesDto as CoordinatesDto } from './coordinates.dto';
export class AddressDto extends CoordinatesDto { export class AddressDto extends CoordinatesDto {
@IsOptional() @IsOptional()
@AutoMap()
name?: string; name?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@AutoMap()
houseNumber?: string; houseNumber?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@AutoMap()
street?: string; street?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@AutoMap()
locality?: string; locality?: string;
@IsOptional() @IsOptional()
@IsString() @IsString()
@AutoMap()
postalCode?: string; postalCode?: string;
@IsString() @IsString()
@AutoMap()
country: string; country: string;
} }

View File

@ -1,4 +1,3 @@
import { AutoMap } from '@automapper/classes';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsLatitude, IsLongitude } from 'class-validator'; import { IsLatitude, IsLongitude } from 'class-validator';
import { toPrecision } from './transformers/to-precision'; import { toPrecision } from './transformers/to-precision';
@ -8,13 +7,11 @@ export class CoordinatesDto {
toClassOnly: true, toClassOnly: true,
}) })
@IsLongitude() @IsLongitude()
@AutoMap()
lon: number; lon: number;
@Transform(({ value }) => toPrecision(value, 6), { @Transform(({ value }) => toPrecision(value, 6), {
toClassOnly: true, toClassOnly: true,
}) })
@IsLatitude() @IsLatitude()
@AutoMap()
lat: number; lat: number;
} }

View File

@ -1,4 +1,3 @@
import { AutoMap } from '@automapper/classes';
import { import {
IsOptional, IsOptional,
IsBoolean, IsBoolean,
@ -21,65 +20,54 @@ import { Frequency } from '@modules/ad/core/ad.types';
export class CreateAdRequestDto { export class CreateAdRequestDto {
@IsUUID(4) @IsUUID(4)
@AutoMap()
userId: string; userId: string;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@AutoMap()
driver?: boolean; driver?: boolean;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@AutoMap()
passenger?: boolean; passenger?: boolean;
@Transform(({ value }) => intToFrequency(value), { @Transform(({ value }) => intToFrequency(value), {
toClassOnly: true, toClassOnly: true,
}) })
@IsEnum(Frequency) @IsEnum(Frequency)
@AutoMap()
frequency: Frequency; frequency: Frequency;
@IsISO8601({ @IsISO8601({
strict: true, strict: true,
strictSeparator: true, strictSeparator: true,
}) })
@AutoMap()
fromDate: string; fromDate: string;
@IsISO8601({ @IsISO8601({
strict: true, strict: true,
strictSeparator: true, strictSeparator: true,
}) })
@AutoMap()
toDate: string; toDate: string;
@Type(() => ScheduleDto) @Type(() => ScheduleDto)
@IsSchedule() @IsSchedule()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@AutoMap()
schedule: ScheduleDto; schedule: ScheduleDto;
@IsOptional() @IsOptional()
@Type(() => MarginDurationsDto) @Type(() => MarginDurationsDto)
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@AutoMap()
marginDurations?: MarginDurationsDto; marginDurations?: MarginDurationsDto;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
seatsProposed?: number; seatsProposed?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
seatsRequested?: number; seatsRequested?: number;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
@AutoMap()
strict?: boolean; strict?: boolean;
@Type(() => WaypointDto) @Type(() => WaypointDto)
@ -87,6 +75,5 @@ export class CreateAdRequestDto {
@ArrayMinSize(2) @ArrayMinSize(2)
@HasValidPositionIndexes() @HasValidPositionIndexes()
@ValidateNested({ each: true }) @ValidateNested({ each: true })
@AutoMap()
waypoints: WaypointDto[]; waypoints: WaypointDto[];
} }

View File

@ -1,39 +1,31 @@
import { AutoMap } from '@automapper/classes';
import { IsInt, IsOptional } from 'class-validator'; import { IsInt, IsOptional } from 'class-validator';
export class MarginDurationsDto { export class MarginDurationsDto {
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
mon?: number; mon?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
tue?: number; tue?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
wed?: number; wed?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
thu?: number; thu?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
fri?: number; fri?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
sat?: number; sat?: number;
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
sun?: number; sun?: number;
} }

View File

@ -1,39 +1,31 @@
import { AutoMap } from '@automapper/classes';
import { IsOptional, IsMilitaryTime } from 'class-validator'; import { IsOptional, IsMilitaryTime } from 'class-validator';
export class ScheduleDto { export class ScheduleDto {
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
mon?: string; mon?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
tue?: string; tue?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
wed?: string; wed?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
thu?: string; thu?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
fri?: string; fri?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
sat?: string; sat?: string;
@IsOptional() @IsOptional()
@IsMilitaryTime() @IsMilitaryTime()
@AutoMap()
sun?: string; sun?: string;
} }

View File

@ -1,10 +1,8 @@
import { AutoMap } from '@automapper/classes';
import { IsInt, IsOptional } from 'class-validator'; import { IsInt, IsOptional } from 'class-validator';
import { AddressDto } from './address.dto'; import { AddressDto } from './address.dto';
export class WaypointDto extends AddressDto { export class WaypointDto extends AddressDto {
@IsOptional() @IsOptional()
@IsInt() @IsInt()
@AutoMap()
position?: number; position?: number;
} }

View File

@ -0,0 +1,132 @@
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { AdEntity } from '@modules/ad/core/ad.entity';
import {
CreateAdProps,
DefaultAdProps,
Frequency,
} from '@modules/ad/core/ad.types';
import { FindAdByIdQuery } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query';
import { FindAdByIdQueryHandler } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler';
import { MarginDurationsProps } from '@modules/ad/core/value-objects/margin-durations.value-object';
import { WaypointProps } from '@modules/ad/core/value-objects/waypoint.value-object';
import { Test, TestingModule } from '@nestjs/testing';
const originWaypointProps: WaypointProps = {
position: 0,
address: {
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
coordinates: {
lon: 48.68944505415954,
lat: 6.176510296462267,
},
},
};
const destinationWaypointProps: WaypointProps = {
position: 1,
address: {
locality: 'Paris',
postalCode: '75000',
country: 'France',
coordinates: {
lon: 48.8566,
lat: 2.3522,
},
},
};
const marginDurationsProps: MarginDurationsProps = {
mon: 600,
tue: 600,
wed: 600,
thu: 600,
fri: 600,
sat: 600,
sun: 600,
};
const baseCreateAdProps = {
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
seatsProposed: 3,
seatsRequested: 1,
strict: false,
waypoints: [originWaypointProps, destinationWaypointProps],
};
const punctualCreateAdProps = {
fromDate: '2023-06-22',
toDate: '2023-06-22',
schedule: {
wed: '08:30',
},
frequency: Frequency.PUNCTUAL,
};
const punctualPassengerCreateAdProps: CreateAdProps = {
...baseCreateAdProps,
...punctualCreateAdProps,
marginDurations: marginDurationsProps,
driver: false,
passenger: true,
};
const defaultAdProps: DefaultAdProps = {
driver: false,
passenger: true,
marginDurations: {
mon: 900,
tue: 900,
wed: 900,
thu: 900,
fri: 900,
sat: 900,
sun: 900,
},
seatsProposed: 3,
seatsRequested: 1,
strict: false,
};
const ad: AdEntity = AdEntity.create(
punctualPassengerCreateAdProps,
defaultAdProps,
);
const mockAdRepository = {
findOneById: jest.fn().mockImplementation(() => ad),
};
describe('find-ad-by-id.query-handler', () => {
let findAdByIdQueryHandler: FindAdByIdQueryHandler;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AD_REPOSITORY,
useValue: mockAdRepository,
},
FindAdByIdQueryHandler,
],
}).compile();
findAdByIdQueryHandler = module.get<FindAdByIdQueryHandler>(
FindAdByIdQueryHandler,
);
});
it('should be defined', () => {
expect(findAdByIdQueryHandler).toBeDefined();
});
describe('execution', () => {
it('should return an ad', async () => {
const findAdbyIdQuery = new FindAdByIdQuery(
'dd264806-13b4-4226-9b18-87adf0ad5dd1',
);
const ad: AdEntity = await findAdByIdQueryHandler.execute(
findAdbyIdQuery,
);
expect(ad.getProps().fromDate).toBe('2023-06-22');
});
});
});