Merge branch 'useUtcDates' into 'main'
Use utc dates See merge request v3/service/ad!18
This commit is contained in:
commit
04726cb136
|
@ -16,8 +16,8 @@ REDIS_HOST=v3-redis
|
|||
REDIS_PASSWORD=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# DEFAULT CARPOOL DEPARTURE MARGIN (in seconds)
|
||||
DEPARTURE_MARGIN=900
|
||||
# DEFAULT CARPOOL DEPARTURE TIME MARGIN (in seconds)
|
||||
DEPARTURE_TIME_MARGIN=900
|
||||
|
||||
# DEFAULT ROLE
|
||||
ROLE=passenger
|
||||
|
|
53
README.md
53
README.md
|
@ -56,7 +56,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
}
|
||||
```
|
||||
|
||||
- **Create** : create an ad (note that id is optional, an id (as a uuid) will be automatically attributed if it is not provided)
|
||||
- **Create** : create an ad
|
||||
|
||||
Punctual driver ad :
|
||||
|
||||
|
@ -68,9 +68,12 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
"frequency": "PUNCTUAL",
|
||||
"fromDate": "2023-01-15",
|
||||
"toDate": "2023-01-15",
|
||||
"schedule": {
|
||||
"thu": "09:00"
|
||||
},
|
||||
"schedule": [
|
||||
{
|
||||
"time": "09:00",
|
||||
"margin": 900
|
||||
}
|
||||
],
|
||||
"waypoints": [
|
||||
{
|
||||
"position": 0,
|
||||
|
@ -106,9 +109,12 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
"frequency": "PUNCTUAL",
|
||||
"fromDate": "2023-01-15",
|
||||
"toDate": "2023-01-15",
|
||||
"schedule": {
|
||||
"thu": "09:00"
|
||||
},
|
||||
"schedule": [
|
||||
{
|
||||
"time": "09:00",
|
||||
"margin": 900
|
||||
}
|
||||
],
|
||||
"waypoints": [
|
||||
{
|
||||
"position": 0,
|
||||
|
@ -142,11 +148,20 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
"frequency": "RECURRRENT",
|
||||
"fromDate": "2023-01-15",
|
||||
"toDate": "2023-12-31",
|
||||
"schedule": {
|
||||
"mon": "07:00",
|
||||
"tue": "07:05",
|
||||
"fri": "07:10"
|
||||
},
|
||||
"schedule": [
|
||||
{
|
||||
"day": 1,
|
||||
"time": "07:00"
|
||||
},
|
||||
{
|
||||
"day": 2,
|
||||
"time": "07:05"
|
||||
},
|
||||
{
|
||||
"day": 5,
|
||||
"time": "07:10"
|
||||
}
|
||||
],
|
||||
"waypoints": [
|
||||
{
|
||||
"position": 0,
|
||||
|
@ -172,22 +187,16 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||
|
||||
The list of possible options when creating an ad :
|
||||
|
||||
- id (optional): the id of the ad (as a uuid)
|
||||
- userId: the user id (as a uuid)
|
||||
- driver (boolean, optional): if the ad is a driver ad
|
||||
- passenger (boolean, optional): if the ad is a passenger ad
|
||||
- frequency: `PUNCTUAL` or `RECURRENT`
|
||||
- fromDate: start date for recurrent ad, carpool date for punctual ad
|
||||
- toDate: end date for recurrent ad, same as fromDate for punctual ad
|
||||
- schedule: an object with the departure time for each carpooled day in the week (only the carpooled day for punctual ad)
|
||||
- marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg:
|
||||
|
||||
{
|
||||
"mon": 900,
|
||||
"tue": 850,
|
||||
"fri": 950
|
||||
}
|
||||
|
||||
- schedule: an array of schedule items, as schedule item containing :
|
||||
- the week day as a number, from 0 (sunday) to 6 (saturday) if the ad is recurrent
|
||||
- the departure time (as HH:MM)
|
||||
- the margin around the departure time in seconds (optional)
|
||||
- seatsProposed (optional): number of seats proposed as driver
|
||||
- seatsRequested (optional): number of seats requested as passenger
|
||||
- strict (boolean, optional): if set to true, allow matching only with similar frequency ads
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"name": "@mobicoop/ad",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@mobicoop/ad",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.14",
|
||||
"@grpc/proto-loader": "^0.7.6",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
"@mobicoop/configuration-module": "^1.2.0",
|
||||
"@mobicoop/ddd-library": "^0.3.0",
|
||||
"@mobicoop/ddd-library": "^1.0.0",
|
||||
"@mobicoop/health-module": "^2.0.0",
|
||||
"@mobicoop/message-broker-module": "^1.2.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
|
@ -236,9 +236,8 @@
|
|||
},
|
||||
"node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
|
@ -277,9 +276,8 @@
|
|||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
|
@ -1401,8 +1399,7 @@
|
|||
},
|
||||
"node_modules/@mobicoop/configuration-module": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/configuration-module/-/configuration-module-1.2.0.tgz",
|
||||
"integrity": "sha512-l0iDae7SgVVmjnCa2MBqAr3Er0yn4E7yiG8e7cs4XtNGUKrC1N0Ju56TEAraEYK9aZAZ36TCs06m1fep+rgwFA==",
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@golevelup/nestjs-rabbitmq": "^3.6.0",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
|
@ -1417,9 +1414,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mobicoop/ddd-library": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-0.3.0.tgz",
|
||||
"integrity": "sha512-MoUDqlrDmJkumCFSyW9FY2DLbguT4rytFrmBt9tVNCr2Es6nlz4Ml3HVBwJTZrlJFU79XmiUQ5WAO0MHJt+nAg==",
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.0.0.tgz",
|
||||
"integrity": "sha512-uOF2n2VqgfVP4QldEPGMuR3VPn0U5+XXQw5CK1E/9IHXIgiqdmAnKHX5qUpcr29mKbU5QvQbBuIyMeQqCFVu+w==",
|
||||
"dependencies": {
|
||||
"@nestjs/event-emitter": "^1.4.2",
|
||||
"@nestjs/microservices": "^9.4.0",
|
||||
|
@ -1433,8 +1430,7 @@
|
|||
},
|
||||
"node_modules/@mobicoop/health-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/health-module/-/health-module-2.0.0.tgz",
|
||||
"integrity": "sha512-r/7zrHJKVRTIiZ50ILy3lEUC/9vi6k0TRcYPMS8zcnUssQg+MPcT5DQS9B9tTB2gkKwcCyxOQlZZIppIybFX3A==",
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@grpc/grpc-js": "^1.8.14",
|
||||
"@grpc/proto-loader": "^0.7.7",
|
||||
|
@ -1449,10 +1445,24 @@
|
|||
"@nestjs/common": "^9.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mobicoop/health-module/node_modules/@mobicoop/ddd-library": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-0.3.0.tgz",
|
||||
"integrity": "sha512-MoUDqlrDmJkumCFSyW9FY2DLbguT4rytFrmBt9tVNCr2Es6nlz4Ml3HVBwJTZrlJFU79XmiUQ5WAO0MHJt+nAg==",
|
||||
"dependencies": {
|
||||
"@nestjs/event-emitter": "^1.4.2",
|
||||
"@nestjs/microservices": "^9.4.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^9.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mobicoop/message-broker-module": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/message-broker-module/-/message-broker-module-1.2.0.tgz",
|
||||
"integrity": "sha512-RoSHHK1GyQ/QVDmm3JS/wBfh171oChvyEp6YWmJd12krFLrPVn9MoEvZdyT3I5J31oBiUabMPle5Kdpw+Nrmww==",
|
||||
"license": "AGPL",
|
||||
"dependencies": {
|
||||
"@golevelup/nestjs-rabbitmq": "^3.6.0",
|
||||
"@types/amqplib": "^0.10.1",
|
||||
|
@ -1464,8 +1474,7 @@
|
|||
},
|
||||
"node_modules/@nestjs/axios": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.0.tgz",
|
||||
"integrity": "sha512-ULdH03jDWkS5dy9X69XbUVbhC+0pVnrRcj7bIK/ytTZ76w7CgvTZDJqsIyisg3kNOiljRW/4NIjSf3j6YGvl+g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||
"axios": "^1.3.1",
|
||||
|
@ -2849,8 +2858,7 @@
|
|||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
@ -4520,14 +4528,13 @@
|
|||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
|
@ -5153,9 +5160,8 @@
|
|||
},
|
||||
"node_modules/istanbul-lib-instrument/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
|
@ -5968,9 +5974,8 @@
|
|||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
|
@ -6776,8 +6781,7 @@
|
|||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@mobicoop/ad",
|
||||
"version": "1.2.1",
|
||||
"version": "2.0.0",
|
||||
"description": "Mobicoop V3 Ad",
|
||||
"author": "sbriat",
|
||||
"private": true,
|
||||
|
@ -24,7 +24,7 @@
|
|||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
|
||||
"test:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"migrate": "docker exec v3-auth-api sh -c 'npx prisma migrate dev'",
|
||||
"migrate": "docker exec v3-ad-api sh -c 'npx prisma migrate dev'",
|
||||
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
|
||||
"migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
|
||||
"migrate:deploy": "npx prisma migrate deploy"
|
||||
|
@ -34,7 +34,7 @@
|
|||
"@grpc/proto-loader": "^0.7.6",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
"@mobicoop/configuration-module": "^1.2.0",
|
||||
"@mobicoop/ddd-library": "^0.3.0",
|
||||
"@mobicoop/ddd-library": "^1.0.0",
|
||||
"@mobicoop/health-module": "^2.0.0",
|
||||
"@mobicoop/message-broker-module": "^1.2.0",
|
||||
"@nestjs/common": "^9.0.0",
|
||||
|
|
|
@ -10,20 +10,6 @@ CREATE TABLE "ad" (
|
|||
"frequency" "Frequency" NOT NULL,
|
||||
"fromDate" DATE NOT NULL,
|
||||
"toDate" DATE NOT NULL,
|
||||
"monTime" TIMESTAMPTZ,
|
||||
"tueTime" TIMESTAMPTZ,
|
||||
"wedTime" TIMESTAMPTZ,
|
||||
"thuTime" TIMESTAMPTZ,
|
||||
"friTime" TIMESTAMPTZ,
|
||||
"satTime" TIMESTAMPTZ,
|
||||
"sunTime" TIMESTAMPTZ,
|
||||
"monMargin" INTEGER NOT NULL,
|
||||
"tueMargin" INTEGER NOT NULL,
|
||||
"wedMargin" INTEGER NOT NULL,
|
||||
"thuMargin" INTEGER NOT NULL,
|
||||
"friMargin" INTEGER NOT NULL,
|
||||
"satMargin" INTEGER NOT NULL,
|
||||
"sunMargin" INTEGER NOT NULL,
|
||||
"seatsProposed" SMALLINT NOT NULL,
|
||||
"seatsRequested" SMALLINT NOT NULL,
|
||||
"strict" BOOLEAN NOT NULL,
|
||||
|
@ -33,6 +19,19 @@ CREATE TABLE "ad" (
|
|||
CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "schedule_item" (
|
||||
"uuid" UUID NOT NULL,
|
||||
"adUuid" UUID NOT NULL,
|
||||
"day" INTEGER NOT NULL,
|
||||
"time" TIME(4) NOT NULL,
|
||||
"margin" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "schedule_item_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "waypoint" (
|
||||
"uuid" UUID NOT NULL,
|
||||
|
@ -52,5 +51,8 @@ CREATE TABLE "waypoint" (
|
|||
CONSTRAINT "waypoint_pkey" PRIMARY KEY ("uuid")
|
||||
);
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "schedule_item" ADD CONSTRAINT "schedule_item_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "waypoint" ADD CONSTRAINT "waypoint_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -12,37 +12,37 @@ datasource db {
|
|||
}
|
||||
|
||||
model Ad {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
userUuid String @db.Uuid
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
userUuid String @db.Uuid
|
||||
driver Boolean
|
||||
passenger Boolean
|
||||
frequency Frequency
|
||||
fromDate DateTime @db.Date
|
||||
toDate DateTime @db.Date
|
||||
monTime DateTime? @db.Timestamptz()
|
||||
tueTime DateTime? @db.Timestamptz()
|
||||
wedTime DateTime? @db.Timestamptz()
|
||||
thuTime DateTime? @db.Timestamptz()
|
||||
friTime DateTime? @db.Timestamptz()
|
||||
satTime DateTime? @db.Timestamptz()
|
||||
sunTime DateTime? @db.Timestamptz()
|
||||
monMargin Int
|
||||
tueMargin Int
|
||||
wedMargin Int
|
||||
thuMargin Int
|
||||
friMargin Int
|
||||
satMargin Int
|
||||
sunMargin Int
|
||||
seatsProposed Int @db.SmallInt
|
||||
seatsRequested Int @db.SmallInt
|
||||
fromDate DateTime @db.Date
|
||||
toDate DateTime @db.Date
|
||||
schedule ScheduleItem[]
|
||||
seatsProposed Int @db.SmallInt
|
||||
seatsRequested Int @db.SmallInt
|
||||
strict Boolean
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
waypoints Waypoint[]
|
||||
|
||||
@@map("ad")
|
||||
}
|
||||
|
||||
model ScheduleItem {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
adUuid String @db.Uuid
|
||||
day Int
|
||||
time DateTime @db.Time(4)
|
||||
margin Int
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
Ad Ad @relation(fields: [adUuid], references: [uuid], onDelete: Cascade)
|
||||
|
||||
@@map("schedule_item")
|
||||
}
|
||||
|
||||
model Waypoint {
|
||||
uuid String @id @default(uuid()) @db.Uuid
|
||||
adUuid String @db.Uuid
|
||||
|
|
|
@ -17,7 +17,7 @@ async function bootstrap() {
|
|||
join(__dirname, 'health.proto'),
|
||||
],
|
||||
url: `${process.env.SERVICE_URL}:${process.env.SERVICE_PORT}`,
|
||||
loader: { keepCase: true },
|
||||
loader: { keepCase: true, enums: String },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -2,4 +2,8 @@ export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
|
|||
export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
|
||||
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
||||
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
||||
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
||||
export const OUTPUT_DATETIME_TRANSFORMER = Symbol(
|
||||
'OUTPUT_DATETIME_TRANSFORMER',
|
||||
);
|
||||
export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
|
||||
|
|
|
@ -6,19 +6,14 @@ import {
|
|||
AdWriteModel,
|
||||
AdReadModel,
|
||||
WaypointModel,
|
||||
ScheduleItemModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
import { Frequency } from './core/domain/ad.types';
|
||||
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
|
||||
import { v4 } from 'uuid';
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from './ad.di-tokens';
|
||||
import { TimezoneFinderPort } from './core/application/ports/timezone-finder.port';
|
||||
import { DefaultParamsProviderPort } from './core/application/ports/default-params-provider.port';
|
||||
import { DefaultParams } from './core/application/ports/default-params.type';
|
||||
import { TimeConverterPort } from './core/application/ports/time-converter.port';
|
||||
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
|
||||
import { OUTPUT_DATETIME_TRANSFORMER } from './ad.di-tokens';
|
||||
import { DateTimeTransformerPort } from './core/application/ports/datetime-transformer.port';
|
||||
|
||||
/**
|
||||
* Mapper constructs objects that are used in different layers:
|
||||
|
@ -31,27 +26,13 @@ import { TimeConverterPort } from './core/application/ports/time-converter.port'
|
|||
export class AdMapper
|
||||
implements Mapper<AdEntity, AdReadModel, AdWriteModel, AdResponseDto>
|
||||
{
|
||||
private readonly _defaultParams: DefaultParams;
|
||||
|
||||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER)
|
||||
private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
@Inject(OUTPUT_DATETIME_TRANSFORMER)
|
||||
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
|
||||
) {}
|
||||
|
||||
toPersistence = (entity: AdEntity): AdWriteModel => {
|
||||
const copy = entity.getProps();
|
||||
const { lon, lat } = copy.waypoints[0].address.coordinates;
|
||||
const timezone = this.timezoneFinder.timezones(
|
||||
lon,
|
||||
lat,
|
||||
this._defaultParams.DEFAULT_TIMEZONE,
|
||||
)[0];
|
||||
const now = new Date();
|
||||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
|
@ -61,62 +42,22 @@ export class AdMapper
|
|||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
monTime: copy.schedule.mon
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.mon,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
tueTime: copy.schedule.tue
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.tue,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
wedTime: copy.schedule.wed
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.wed,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
thuTime: copy.schedule.thu
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.thu,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
friTime: copy.schedule.fri
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.fri,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
satTime: copy.schedule.sat
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.sat,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
sunTime: copy.schedule.sun
|
||||
? this.timeConverter.localDateTimeToUtc(
|
||||
copy.fromDate,
|
||||
copy.schedule.sun,
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
monMargin: copy.marginDurations.mon,
|
||||
tueMargin: copy.marginDurations.tue,
|
||||
wedMargin: copy.marginDurations.wed,
|
||||
thuMargin: copy.marginDurations.thu,
|
||||
friMargin: copy.marginDurations.fri,
|
||||
satMargin: copy.marginDurations.sat,
|
||||
sunMargin: copy.marginDurations.sun,
|
||||
schedule: {
|
||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
1,
|
||||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
},
|
||||
seatsProposed: copy.seatsProposed,
|
||||
seatsRequested: copy.seatsRequested,
|
||||
strict: copy.strict,
|
||||
|
@ -143,11 +84,6 @@ export class AdMapper
|
|||
};
|
||||
|
||||
toDomain = (record: AdReadModel): AdEntity => {
|
||||
const timezone = this.timezoneFinder.timezones(
|
||||
record.waypoints[0].lon,
|
||||
record.waypoints[0].lat,
|
||||
this._defaultParams.DEFAULT_TIMEZONE,
|
||||
)[0];
|
||||
const entity = new AdEntity({
|
||||
id: record.uuid,
|
||||
createdAt: new Date(record.createdAt),
|
||||
|
@ -159,34 +95,17 @@ export class AdMapper
|
|||
frequency: Frequency[record.frequency],
|
||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||
toDate: record.toDate.toISOString().split('T')[0],
|
||||
schedule: {
|
||||
mon: record.monTime?.toISOString(),
|
||||
tue: record.tueTime?.toISOString(),
|
||||
wed: record.wedTime
|
||||
? this.timeConverter.utcDatetimeToLocalTime(
|
||||
record.wedTime.toISOString(),
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
thu: record.thuTime
|
||||
? this.timeConverter.utcDatetimeToLocalTime(
|
||||
record.thuTime.toISOString(),
|
||||
timezone,
|
||||
)
|
||||
: undefined,
|
||||
fri: record.friTime?.toISOString(),
|
||||
sat: record.satTime?.toISOString(),
|
||||
sun: record.sunTime?.toISOString(),
|
||||
},
|
||||
marginDurations: {
|
||||
mon: record.monMargin,
|
||||
tue: record.tueMargin,
|
||||
wed: record.wedMargin,
|
||||
thu: record.thuMargin,
|
||||
fri: record.friMargin,
|
||||
sat: record.satMargin,
|
||||
sun: record.sunMargin,
|
||||
},
|
||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
||||
day: scheduleItem.day,
|
||||
time: `${scheduleItem.time
|
||||
.getUTCHours()
|
||||
.toString()
|
||||
.padStart(2, '0')}:${scheduleItem.time
|
||||
.getUTCMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}`,
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: record.seatsProposed,
|
||||
seatsRequested: record.seatsRequested,
|
||||
strict: record.strict,
|
||||
|
@ -217,10 +136,45 @@ export class AdMapper
|
|||
response.driver = props.driver;
|
||||
response.passenger = props.passenger;
|
||||
response.frequency = props.frequency;
|
||||
response.fromDate = props.fromDate;
|
||||
response.toDate = props.toDate;
|
||||
response.schedule = { ...props.schedule };
|
||||
response.marginDurations = { ...props.marginDurations };
|
||||
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: props.schedule[0].time,
|
||||
coordinates: props.waypoints[0].address.coordinates,
|
||||
},
|
||||
props.frequency,
|
||||
);
|
||||
response.toDate = this.outputDatetimeTransformer.toDate(
|
||||
props.toDate,
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: props.schedule[0].time,
|
||||
coordinates: props.waypoints[0].address.coordinates,
|
||||
},
|
||||
props.frequency,
|
||||
);
|
||||
response.schedule = props.schedule.map(
|
||||
(scheduleItem: ScheduleItemProps) => ({
|
||||
day: this.outputDatetimeTransformer.day(
|
||||
scheduleItem.day,
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: scheduleItem.time,
|
||||
coordinates: props.waypoints[0].address.coordinates,
|
||||
},
|
||||
props.frequency,
|
||||
),
|
||||
time: this.outputDatetimeTransformer.time(
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: scheduleItem.time,
|
||||
coordinates: props.waypoints[0].address.coordinates,
|
||||
},
|
||||
props.frequency,
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
}),
|
||||
);
|
||||
response.seatsProposed = props.seatsProposed;
|
||||
response.seatsRequested = props.seatsRequested;
|
||||
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
|
@ -236,12 +190,4 @@ export class AdMapper
|
|||
}));
|
||||
return response;
|
||||
};
|
||||
|
||||
/* ^ Data returned to the user is whitelisted to avoid leaks.
|
||||
If a new property is added, like password or a
|
||||
credit card number, it won't be returned
|
||||
unless you specifically allow this.
|
||||
(avoid blacklisting, which will return everything
|
||||
but blacklisted items, which can lead to a data leak).
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import { CqrsModule } from '@nestjs/cqrs';
|
|||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
|
@ -19,6 +21,8 @@ import { FindAdByIdQueryHandler } from './core/application/queries/find-ad-by-id
|
|||
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
|
||||
import { PrismaService } from './infrastructure/prisma.service';
|
||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||
|
||||
const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController];
|
||||
|
||||
|
@ -60,6 +64,14 @@ const adapters: Provider[] = [
|
|||
provide: TIME_CONVERTER,
|
||||
useClass: TimeConverter,
|
||||
},
|
||||
{
|
||||
provide: INPUT_DATETIME_TRANSFORMER,
|
||||
useClass: InputDateTimeTransformer,
|
||||
},
|
||||
{
|
||||
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||
useClass: OutputDateTimeTransformer,
|
||||
},
|
||||
];
|
||||
|
||||
@Module({
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Schedule } from '../../types/schedule';
|
||||
import { MarginDurations } from '../../types/margin-durations';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
|
@ -11,8 +10,7 @@ export class CreateAdCommand extends Command {
|
|||
readonly frequency?: Frequency;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly schedule: Schedule;
|
||||
readonly marginDurations?: MarginDurations;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly seatsProposed?: number;
|
||||
readonly seatsRequested?: number;
|
||||
readonly strict?: boolean;
|
||||
|
@ -27,7 +25,6 @@ export class CreateAdCommand extends Command {
|
|||
this.fromDate = props.fromDate;
|
||||
this.toDate = props.toDate;
|
||||
this.schedule = props.schedule;
|
||||
this.marginDurations = props.marginDurations;
|
||||
this.seatsProposed = props.seatsProposed;
|
||||
this.seatsRequested = props.seatsRequested;
|
||||
this.strict = props.strict;
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { CreateAdCommand } from './create-ad.command';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { DefaultParams } from '../../ports/default-params.type';
|
||||
|
@ -9,6 +13,8 @@ import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
|||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||
|
||||
@CommandHandler(CreateAdCommand)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
|
@ -19,6 +25,8 @@ export class CreateAdService implements ICommandHandler {
|
|||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(INPUT_DATETIME_TRANSFORMER)
|
||||
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
|
@ -30,10 +38,55 @@ export class CreateAdService implements ICommandHandler {
|
|||
driver: command.driver,
|
||||
passenger: command.passenger,
|
||||
frequency: command.frequency,
|
||||
fromDate: command.fromDate,
|
||||
toDate: command.toDate,
|
||||
schedule: command.schedule,
|
||||
marginDurations: command.marginDurations,
|
||||
fromDate: this.datetimeTransformer.fromDate(
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: command.schedule[0].time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
},
|
||||
},
|
||||
command.frequency,
|
||||
),
|
||||
toDate: this.datetimeTransformer.toDate(
|
||||
command.toDate,
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: command.schedule[0].time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
},
|
||||
},
|
||||
command.frequency,
|
||||
),
|
||||
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
|
||||
day: this.datetimeTransformer.day(
|
||||
scheduleItem.day,
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: scheduleItem.time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
},
|
||||
},
|
||||
command.frequency,
|
||||
),
|
||||
time: this.datetimeTransformer.time(
|
||||
{
|
||||
date: command.fromDate,
|
||||
time: scheduleItem.time,
|
||||
coordinates: {
|
||||
lon: command.waypoints[0].lon,
|
||||
lat: command.waypoints[0].lat,
|
||||
},
|
||||
},
|
||||
command.frequency,
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: command.seatsProposed,
|
||||
seatsRequested: command.seatsRequested,
|
||||
strict: command.strict,
|
||||
|
@ -56,15 +109,7 @@ export class CreateAdService implements ICommandHandler {
|
|||
{
|
||||
driver: this._defaultParams.DRIVER,
|
||||
passenger: this._defaultParams.PASSENGER,
|
||||
marginDurations: {
|
||||
mon: this._defaultParams.MON_MARGIN,
|
||||
tue: this._defaultParams.TUE_MARGIN,
|
||||
wed: this._defaultParams.WED_MARGIN,
|
||||
thu: this._defaultParams.THU_MARGIN,
|
||||
fri: this._defaultParams.FRI_MARGIN,
|
||||
sat: this._defaultParams.SAT_MARGIN,
|
||||
sun: this._defaultParams.SUN_MARGIN,
|
||||
},
|
||||
marginDuration: this._defaultParams.DEPARTURE_TIME_MARGIN,
|
||||
strict: this._defaultParams.STRICT,
|
||||
seatsProposed: this._defaultParams.SEATS_PROPOSED,
|
||||
seatsRequested: this._defaultParams.SEATS_REQUESTED,
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
export interface DateTimeTransformerPort {
|
||||
fromDate(geoFromDate: GeoDateTime, frequency: Frequency): string;
|
||||
toDate(
|
||||
toDate: string,
|
||||
geoFromDate: GeoDateTime,
|
||||
frequency: Frequency,
|
||||
): string;
|
||||
day(day: number, geoFromDate: GeoDateTime, frequency: Frequency): number;
|
||||
time(geoFromDate: GeoDateTime, frequency: Frequency): string;
|
||||
}
|
||||
|
||||
export type GeoDateTime = {
|
||||
date: string;
|
||||
time: string;
|
||||
coordinates: Coordinates;
|
||||
};
|
||||
|
||||
export type Coordinates = {
|
||||
lon: number;
|
||||
lat: number;
|
||||
};
|
||||
|
||||
export enum Frequency {
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
export type DefaultParams = {
|
||||
MON_MARGIN: number;
|
||||
TUE_MARGIN: number;
|
||||
WED_MARGIN: number;
|
||||
THU_MARGIN: number;
|
||||
FRI_MARGIN: number;
|
||||
SAT_MARGIN: number;
|
||||
SUN_MARGIN: number;
|
||||
DRIVER: boolean;
|
||||
SEATS_PROPOSED: number;
|
||||
PASSENGER: boolean;
|
||||
SEATS_PROPOSED: number;
|
||||
SEATS_REQUESTED: number;
|
||||
DEPARTURE_TIME_MARGIN: number;
|
||||
STRICT: boolean;
|
||||
DEFAULT_TIMEZONE: string;
|
||||
};
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
export interface TimeConverterPort {
|
||||
localDateTimeToUtc(
|
||||
localStringTimeToUtcStringTime(time: string, timezone: string): string;
|
||||
utcStringTimeToLocalStringTime(time: string, timezone: string): string;
|
||||
localStringDateTimeToUtcDate(
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
): Date;
|
||||
utcDatetimeToLocalTime(isoString: string, timezone: string): string;
|
||||
utcStringDateTimeToLocalIsoString(
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
): string;
|
||||
utcUnixEpochDayFromTime(time: string, timezone: string): number;
|
||||
localUnixEpochDayFromTime(time: string, timezone: string): number;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ export class FindAdByIdQueryHandler implements IQueryHandler {
|
|||
private readonly repository: AdRepositoryPort,
|
||||
) {}
|
||||
async execute(query: FindAdByIdQuery): Promise<AdEntity> {
|
||||
return await this.repository.findOneById(query.id, { waypoints: true });
|
||||
return await this.repository.findOneById(query.id, {
|
||||
waypoints: true,
|
||||
schedule: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export type ScheduleItem = {
|
||||
day?: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
export type Schedule = {
|
||||
mon?: string;
|
||||
tue?: string;
|
||||
wed?: string;
|
||||
thu?: string;
|
||||
fri?: string;
|
||||
sat?: string;
|
||||
sun?: string;
|
||||
};
|
|
@ -2,8 +2,8 @@ import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
|||
import { v4 } from 'uuid';
|
||||
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
||||
import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types';
|
||||
import { Waypoint } from './value-objects/waypoint.value-object';
|
||||
import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
export class AdEntity extends AggregateRoot<AdProps> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
@ -15,7 +15,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
const id = v4();
|
||||
const props: AdProps = { ...create };
|
||||
const ad = new AdEntity({ id, props })
|
||||
.setMissingMarginDurations(defaultAdProps.marginDurations)
|
||||
.setMissingMarginDurations(defaultAdProps.marginDuration)
|
||||
.setMissingStrict(defaultAdProps.strict)
|
||||
.setDefaultDriverAndPassengerParameters({
|
||||
driver: defaultAdProps.driver,
|
||||
|
@ -33,24 +33,15 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
frequency: props.frequency,
|
||||
fromDate: props.fromDate,
|
||||
toDate: props.toDate,
|
||||
monTime: props.schedule.mon,
|
||||
tueTime: props.schedule.tue,
|
||||
wedTime: props.schedule.wed,
|
||||
thuTime: props.schedule.thu,
|
||||
friTime: props.schedule.fri,
|
||||
satTime: props.schedule.sat,
|
||||
sunTime: props.schedule.sun,
|
||||
monMarginDuration: props.marginDurations.mon,
|
||||
tueMarginDuration: props.marginDurations.tue,
|
||||
wedMarginDuration: props.marginDurations.wed,
|
||||
thuMarginDuration: props.marginDurations.thu,
|
||||
friMarginDuration: props.marginDurations.fri,
|
||||
satMarginDuration: props.marginDurations.sat,
|
||||
sunMarginDuration: props.marginDurations.sun,
|
||||
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
||||
day: day.day,
|
||||
time: day.time,
|
||||
margin: day.margin,
|
||||
})),
|
||||
seatsProposed: props.seatsProposed,
|
||||
seatsRequested: props.seatsRequested,
|
||||
strict: props.strict,
|
||||
waypoints: props.waypoints.map((waypoint: Waypoint) => ({
|
||||
waypoints: props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
position: waypoint.position,
|
||||
name: waypoint.address.name,
|
||||
houseNumber: waypoint.address.houseNumber,
|
||||
|
@ -67,23 +58,11 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||
};
|
||||
|
||||
private setMissingMarginDurations = (
|
||||
defaultMarginDurations: MarginDurationsProps,
|
||||
defaultMarginDuration: number,
|
||||
): AdEntity => {
|
||||
if (!this.props.marginDurations) this.props.marginDurations = {};
|
||||
if (!this.props.marginDurations.mon)
|
||||
this.props.marginDurations.mon = defaultMarginDurations.mon;
|
||||
if (!this.props.marginDurations.tue)
|
||||
this.props.marginDurations.tue = defaultMarginDurations.tue;
|
||||
if (!this.props.marginDurations.wed)
|
||||
this.props.marginDurations.wed = defaultMarginDurations.wed;
|
||||
if (!this.props.marginDurations.thu)
|
||||
this.props.marginDurations.thu = defaultMarginDurations.thu;
|
||||
if (!this.props.marginDurations.fri)
|
||||
this.props.marginDurations.fri = defaultMarginDurations.fri;
|
||||
if (!this.props.marginDurations.sat)
|
||||
this.props.marginDurations.sat = defaultMarginDurations.sat;
|
||||
if (!this.props.marginDurations.sun)
|
||||
this.props.marginDurations.sun = defaultMarginDurations.sun;
|
||||
this.props.schedule.forEach((day: ScheduleItemProps) => {
|
||||
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { MarginDurationsProps } from './value-objects/margin-durations.value-object';
|
||||
import { ScheduleProps } from './value-objects/schedule.value-object';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
// All properties that an Ad has
|
||||
|
@ -10,8 +9,7 @@ export interface AdProps {
|
|||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: ScheduleProps;
|
||||
marginDurations: MarginDurationsProps;
|
||||
schedule: ScheduleItemProps[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -26,8 +24,7 @@ export interface CreateAdProps {
|
|||
frequency: Frequency;
|
||||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: ScheduleProps;
|
||||
marginDurations: MarginDurationsProps;
|
||||
schedule: ScheduleItemProps[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -37,7 +34,7 @@ export interface CreateAdProps {
|
|||
export interface DefaultAdProps {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
marginDurations: MarginDurationsProps;
|
||||
marginDuration: number;
|
||||
strict: boolean;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
|
|
|
@ -7,20 +7,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
readonly frequency: string;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly monTime: string;
|
||||
readonly tueTime: string;
|
||||
readonly wedTime: string;
|
||||
readonly thuTime: string;
|
||||
readonly friTime: string;
|
||||
readonly satTime: string;
|
||||
readonly sunTime: string;
|
||||
readonly monMarginDuration: number;
|
||||
readonly tueMarginDuration: number;
|
||||
readonly wedMarginDuration: number;
|
||||
readonly thuMarginDuration: number;
|
||||
readonly friMarginDuration: number;
|
||||
readonly satMarginDuration: number;
|
||||
readonly sunMarginDuration: number;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly seatsProposed: number;
|
||||
readonly seatsRequested: number;
|
||||
readonly strict: boolean;
|
||||
|
@ -34,20 +21,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
this.frequency = props.frequency;
|
||||
this.fromDate = props.fromDate;
|
||||
this.toDate = props.toDate;
|
||||
this.monTime = props.monTime;
|
||||
this.tueTime = props.tueTime;
|
||||
this.wedTime = props.wedTime;
|
||||
this.thuTime = props.thuTime;
|
||||
this.friTime = props.friTime;
|
||||
this.satTime = props.satTime;
|
||||
this.sunTime = props.sunTime;
|
||||
this.monMarginDuration = props.monMarginDuration;
|
||||
this.tueMarginDuration = props.tueMarginDuration;
|
||||
this.wedMarginDuration = props.wedMarginDuration;
|
||||
this.thuMarginDuration = props.thuMarginDuration;
|
||||
this.friMarginDuration = props.friMarginDuration;
|
||||
this.satMarginDuration = props.satMarginDuration;
|
||||
this.sunMarginDuration = props.sunMarginDuration;
|
||||
this.schedule = props.schedule;
|
||||
this.seatsProposed = props.seatsProposed;
|
||||
this.seatsRequested = props.seatsRequested;
|
||||
this.strict = props.strict;
|
||||
|
@ -55,6 +29,12 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||
}
|
||||
}
|
||||
|
||||
export class ScheduleItem {
|
||||
day: number;
|
||||
time: string;
|
||||
margin: number;
|
||||
}
|
||||
|
||||
export class Waypoint {
|
||||
position: number;
|
||||
name?: string;
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface MarginDurationsProps {
|
||||
mon?: number;
|
||||
tue?: number;
|
||||
wed?: number;
|
||||
thu?: number;
|
||||
fri?: number;
|
||||
sat?: number;
|
||||
sun?: number;
|
||||
}
|
||||
|
||||
export class MarginDurations extends ValueObject<MarginDurationsProps> {
|
||||
get mon(): number {
|
||||
return this.props.mon;
|
||||
}
|
||||
|
||||
set mon(margin: number) {
|
||||
this.props.mon = margin;
|
||||
}
|
||||
|
||||
get tue(): number {
|
||||
return this.props.tue;
|
||||
}
|
||||
|
||||
set tue(margin: number) {
|
||||
this.props.tue = margin;
|
||||
}
|
||||
|
||||
get wed(): number {
|
||||
return this.props.wed;
|
||||
}
|
||||
|
||||
set wed(margin: number) {
|
||||
this.props.wed = margin;
|
||||
}
|
||||
|
||||
get thu(): number {
|
||||
return this.props.thu;
|
||||
}
|
||||
|
||||
set thu(margin: number) {
|
||||
this.props.thu = margin;
|
||||
}
|
||||
|
||||
get fri(): number {
|
||||
return this.props.fri;
|
||||
}
|
||||
|
||||
set fri(margin: number) {
|
||||
this.props.fri = margin;
|
||||
}
|
||||
|
||||
get sat(): number {
|
||||
return this.props.sat;
|
||||
}
|
||||
|
||||
set sat(margin: number) {
|
||||
this.props.sat = margin;
|
||||
}
|
||||
|
||||
get sun(): number {
|
||||
return this.props.sun;
|
||||
}
|
||||
|
||||
set sun(margin: number) {
|
||||
this.props.sun = margin;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: MarginDurationsProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface ScheduleItemProps {
|
||||
day?: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
export class ScheduleItem extends ValueObject<ScheduleItemProps> {
|
||||
get day(): number | undefined {
|
||||
return this.props.day;
|
||||
}
|
||||
|
||||
get time(): string {
|
||||
return this.props.time;
|
||||
}
|
||||
|
||||
get margin(): number | undefined {
|
||||
return this.props.margin;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: ScheduleItemProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
import { ValueObject } from '@mobicoop/ddd-library';
|
||||
|
||||
/** Note:
|
||||
* Value Objects with multiple properties can contain
|
||||
* other Value Objects inside if needed.
|
||||
* */
|
||||
|
||||
export interface ScheduleProps {
|
||||
mon?: string;
|
||||
tue?: string;
|
||||
wed?: string;
|
||||
thu?: string;
|
||||
fri?: string;
|
||||
sat?: string;
|
||||
sun?: string;
|
||||
}
|
||||
|
||||
export class Schedule extends ValueObject<ScheduleProps> {
|
||||
get mon(): string | undefined {
|
||||
return this.props.mon;
|
||||
}
|
||||
|
||||
get tue(): string | undefined {
|
||||
return this.props.tue;
|
||||
}
|
||||
|
||||
get wed(): string | undefined {
|
||||
return this.props.wed;
|
||||
}
|
||||
|
||||
get thu(): string | undefined {
|
||||
return this.props.thu;
|
||||
}
|
||||
|
||||
get fri(): string | undefined {
|
||||
return this.props.fri;
|
||||
}
|
||||
|
||||
get sat(): string | undefined {
|
||||
return this.props.sat;
|
||||
}
|
||||
|
||||
get sun(): string | undefined {
|
||||
return this.props.sun;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected validate(props: ScheduleProps): void {
|
||||
return;
|
||||
}
|
||||
}
|
|
@ -19,20 +19,6 @@ export type AdBaseModel = {
|
|||
frequency: string;
|
||||
fromDate: Date;
|
||||
toDate: Date;
|
||||
monTime: Date;
|
||||
tueTime: Date;
|
||||
wedTime: Date;
|
||||
thuTime: Date;
|
||||
friTime: Date;
|
||||
satTime: Date;
|
||||
sunTime: Date;
|
||||
monMargin: number;
|
||||
tueMargin: number;
|
||||
wedMargin: number;
|
||||
thuMargin: number;
|
||||
friMargin: number;
|
||||
satMargin: number;
|
||||
sunMargin: number;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
@ -42,12 +28,25 @@ export type AdBaseModel = {
|
|||
|
||||
export type AdReadModel = AdBaseModel & {
|
||||
waypoints: WaypointModel[];
|
||||
schedule: ScheduleItemModel[];
|
||||
};
|
||||
|
||||
export type AdWriteModel = AdBaseModel & {
|
||||
waypoints: {
|
||||
create: WaypointModel[];
|
||||
};
|
||||
schedule: {
|
||||
create: ScheduleItemModel[];
|
||||
};
|
||||
};
|
||||
|
||||
export type ScheduleItemModel = {
|
||||
uuid: string;
|
||||
day: number;
|
||||
time: Date;
|
||||
margin: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
export type WaypointModel = {
|
||||
|
|
|
@ -7,17 +7,13 @@ import { DefaultParams } from '../core/application/ports/default-params.type';
|
|||
export class DefaultParamsProvider implements DefaultParamsProviderPort {
|
||||
constructor(private readonly _configService: ConfigService) {}
|
||||
getParams = (): DefaultParams => ({
|
||||
MON_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
TUE_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
WED_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
THU_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
FRI_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
SAT_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
SUN_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')),
|
||||
DRIVER: this._configService.get('ROLE') == 'driver',
|
||||
SEATS_PROPOSED: parseInt(this._configService.get('SEATS_PROPOSED')),
|
||||
PASSENGER: this._configService.get('ROLE') == 'passenger',
|
||||
SEATS_REQUESTED: parseInt(this._configService.get('SEATS_REQUESTED')),
|
||||
DEPARTURE_TIME_MARGIN: parseInt(
|
||||
this._configService.get('DEPARTURE_TIME_MARGIN'),
|
||||
),
|
||||
STRICT: this._configService.get('STRICT_FREQUENCY') == 'true',
|
||||
DEFAULT_TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'),
|
||||
});
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
DateTimeTransformerPort,
|
||||
Frequency,
|
||||
GeoDateTime,
|
||||
} from '../core/application/ports/datetime-transformer.port';
|
||||
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '../ad.di-tokens';
|
||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
||||
|
||||
@Injectable()
|
||||
export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
private readonly _defaultTimezone: string;
|
||||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultTimezone = defaultParamsProvider.getParams().DEFAULT_TIMEZONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the fromDate : if an ad is punctual, the departure date
|
||||
* is converted to UTC with the time and timezone
|
||||
*/
|
||||
fromDate = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||
if (frequency === Frequency.RECURRENT) return geoFromDate.date;
|
||||
return this.timeConverter
|
||||
.localStringDateTimeToUtcDate(
|
||||
geoFromDate.date,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
.split('T')[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the toDate depending on frequency, time and timezone :
|
||||
* if the ad is punctual, the toDate is equal to the fromDate
|
||||
*/
|
||||
toDate = (
|
||||
toDate: string,
|
||||
geoFromDate: GeoDateTime,
|
||||
frequency: Frequency,
|
||||
): string => {
|
||||
if (frequency === Frequency.RECURRENT) return toDate;
|
||||
return this.fromDate(geoFromDate, frequency);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the day for a schedule item :
|
||||
* - if the ad is punctual, the day is infered from fromDate
|
||||
* - if the ad is recurrent, the day is computed by converting the time to utc
|
||||
*/
|
||||
day = (
|
||||
day: number,
|
||||
geoFromDate: GeoDateTime,
|
||||
frequency: Frequency,
|
||||
): number => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.recurrentDay(
|
||||
day,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return new Date(this.fromDate(geoFromDate, frequency)).getDay();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the utc time
|
||||
*/
|
||||
time = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.timeConverter.localStringTimeToUtcStringTime(
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return this.timeConverter
|
||||
.localStringDateTimeToUtcDate(
|
||||
geoFromDate.date,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
.split('T')[1]
|
||||
.split(':', 2)
|
||||
.join(':');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the day for a schedule item for a recurrent ad
|
||||
* The day may change when transforming from local timezone to utc
|
||||
*/
|
||||
private recurrentDay = (
|
||||
day: number,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): number => {
|
||||
const unixEpochDay = 4; // 1970-01-01 is a thursday !
|
||||
const utcBaseDay = this.timeConverter.utcUnixEpochDayFromTime(
|
||||
time,
|
||||
timezone,
|
||||
);
|
||||
if (unixEpochDay == utcBaseDay) return day;
|
||||
if (unixEpochDay > utcBaseDay) return day > 0 ? day - 1 : 6;
|
||||
return day < 6 ? day + 1 : 0;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import {
|
||||
DateTimeTransformerPort,
|
||||
Frequency,
|
||||
GeoDateTime,
|
||||
} from '../core/application/ports/datetime-transformer.port';
|
||||
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||
import { TIMEZONE_FINDER, TIME_CONVERTER } from '../ad.di-tokens';
|
||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||
|
||||
@Injectable()
|
||||
export class OutputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
constructor(
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Compute the fromDate : if an ad is punctual, the departure date
|
||||
* is converted from UTC to the local date with the time and timezone
|
||||
*/
|
||||
fromDate = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||
if (frequency === Frequency.RECURRENT) return geoFromDate.date;
|
||||
return this.timeConverter
|
||||
.utcStringDateTimeToLocalIsoString(
|
||||
geoFromDate.date,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
)[0],
|
||||
)
|
||||
.split('T')[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the toDate depending on frequency, time and timezone :
|
||||
* if the ad is punctual, the toDate is equal to the fromDate
|
||||
*/
|
||||
toDate = (
|
||||
toDate: string,
|
||||
geoFromDate: GeoDateTime,
|
||||
frequency: Frequency,
|
||||
): string => {
|
||||
if (frequency === Frequency.RECURRENT) return toDate;
|
||||
return this.fromDate(geoFromDate, frequency);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the day for a schedule item :
|
||||
* - if the ad is punctual, the day is infered from fromDate
|
||||
* - if the ad is recurrent, the day is computed by converting the time from utc to local time
|
||||
*/
|
||||
day = (
|
||||
day: number,
|
||||
geoFromDate: GeoDateTime,
|
||||
frequency: Frequency,
|
||||
): number => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.recurrentDay(
|
||||
day,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
)[0],
|
||||
);
|
||||
return new Date(this.fromDate(geoFromDate, frequency)).getDay();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the utc time
|
||||
*/
|
||||
time = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
|
||||
if (frequency === Frequency.RECURRENT)
|
||||
return this.timeConverter.utcStringTimeToLocalStringTime(
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
)[0],
|
||||
);
|
||||
return this.timeConverter
|
||||
.utcStringDateTimeToLocalIsoString(
|
||||
geoFromDate.date,
|
||||
geoFromDate.time,
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
)[0],
|
||||
)
|
||||
.split('T')[1]
|
||||
.split(':', 2)
|
||||
.join(':');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the day for a schedule item for a recurrent ad
|
||||
* The day may change when transforming from utc to local timezone
|
||||
*/
|
||||
private recurrentDay = (
|
||||
day: number,
|
||||
time: string,
|
||||
timezone: string,
|
||||
): number => {
|
||||
const unixEpochDay = 4; // 1970-01-01 is a thursday !
|
||||
const localBaseDay = this.timeConverter.localUnixEpochDayFromTime(
|
||||
time,
|
||||
timezone,
|
||||
);
|
||||
if (unixEpochDay == localBaseDay) return day;
|
||||
if (unixEpochDay > localBaseDay) return day > 0 ? day - 1 : 6;
|
||||
return day < 6 ? day + 1 : 0;
|
||||
};
|
||||
}
|
|
@ -4,31 +4,91 @@ import { TimeConverterPort } from '../core/application/ports/time-converter.port
|
|||
|
||||
@Injectable()
|
||||
export class TimeConverter implements TimeConverterPort {
|
||||
localDateTimeToUtc = (
|
||||
private readonly UNIX_EPOCH = '1970-01-01';
|
||||
|
||||
localStringTimeToUtcStringTime = (time: string, timezone: string): string => {
|
||||
try {
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone(timezone))
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.format('HH:mm');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
utcStringTimeToLocalStringTime = (time: string, timezone: string): string => {
|
||||
try {
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone('UTC'))
|
||||
.convert(TimeZone.zone(timezone))
|
||||
.format('HH:mm');
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
localStringDateTimeToUtcDate = (
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
dst = true,
|
||||
): Date => {
|
||||
try {
|
||||
if (!date || !time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst))
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toIsoString(),
|
||||
new DateTime(
|
||||
`${date}T${time}`,
|
||||
TimeZone.zone(timezone, dst),
|
||||
).toIsoString(),
|
||||
);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
utcDatetimeToLocalTime = (isoString: string, timezone: string): string => {
|
||||
utcStringDateTimeToLocalIsoString = (
|
||||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
): string => {
|
||||
try {
|
||||
return new DateTime(isoString)
|
||||
.convert(TimeZone.zone(timezone))
|
||||
.toString()
|
||||
.split('T')[1]
|
||||
.substring(0, 5);
|
||||
if (!date || !time || !timezone) throw new Error();
|
||||
return new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
|
||||
.convert(TimeZone.zone(timezone, dst))
|
||||
.toIsoString();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
utcUnixEpochDayFromTime = (time: string, timezone: string): number => {
|
||||
try {
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(
|
||||
`${this.UNIX_EPOCH}T${time}`,
|
||||
TimeZone.zone(timezone, false),
|
||||
)
|
||||
.convert(TimeZone.zone('UTC'))
|
||||
.toIsoString()
|
||||
.split('T')[0],
|
||||
).getDay();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
localUnixEpochDayFromTime = (time: string, timezone: string): number => {
|
||||
try {
|
||||
if (!time || !timezone) throw new Error();
|
||||
return new Date(
|
||||
new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone('UTC'))
|
||||
.convert(TimeZone.zone(timezone))
|
||||
.toIsoString()
|
||||
.split('T')[0],
|
||||
).getDay();
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -9,23 +9,10 @@ export class AdResponseDto extends ResponseBase {
|
|||
fromDate: string;
|
||||
toDate: string;
|
||||
schedule: {
|
||||
mon?: string;
|
||||
tue?: string;
|
||||
wed?: string;
|
||||
thu?: string;
|
||||
fri?: string;
|
||||
sat?: string;
|
||||
sun?: string;
|
||||
};
|
||||
marginDurations: {
|
||||
mon?: number;
|
||||
tue?: number;
|
||||
wed?: number;
|
||||
thu?: number;
|
||||
fri?: number;
|
||||
sat?: number;
|
||||
sun?: number;
|
||||
};
|
||||
day: number;
|
||||
time: string;
|
||||
margin: number;
|
||||
}[];
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
strict: boolean;
|
||||
|
|
|
@ -2,7 +2,7 @@ syntax = "proto3";
|
|||
|
||||
package ad;
|
||||
|
||||
service AdsService {
|
||||
service AdService {
|
||||
rpc FindOneById(AdById) returns (Ad);
|
||||
rpc FindAll(AdFilter) returns (Ads);
|
||||
rpc Create(Ad) returns (AdById);
|
||||
|
@ -22,38 +22,23 @@ message Ad {
|
|||
Frequency frequency = 5;
|
||||
string fromDate = 6;
|
||||
string toDate = 7;
|
||||
Schedule schedule = 8;
|
||||
MarginDurations marginDurations = 9;
|
||||
int32 seatsProposed = 10;
|
||||
int32 seatsRequested = 11;
|
||||
bool strict = 12;
|
||||
repeated Waypoint waypoints = 13;
|
||||
repeated ScheduleItem schedule = 8;
|
||||
int32 seatsProposed = 9;
|
||||
int32 seatsRequested = 10;
|
||||
bool strict = 11;
|
||||
repeated Waypoint waypoints = 12;
|
||||
}
|
||||
|
||||
message Schedule {
|
||||
string mon = 1;
|
||||
string tue = 2;
|
||||
string wed = 3;
|
||||
string thu = 4;
|
||||
string fri = 5;
|
||||
string sat = 6;
|
||||
string sun = 7;
|
||||
}
|
||||
|
||||
message MarginDurations {
|
||||
int32 mon = 1;
|
||||
int32 tue = 2;
|
||||
int32 wed = 3;
|
||||
int32 thu = 4;
|
||||
int32 fri = 5;
|
||||
int32 sat = 6;
|
||||
int32 sun = 7;
|
||||
message ScheduleItem {
|
||||
int32 day = 1;
|
||||
string time = 2;
|
||||
int32 margin = 3;
|
||||
}
|
||||
|
||||
message Waypoint {
|
||||
int32 position = 1;
|
||||
float lon = 2;
|
||||
float lat = 3;
|
||||
double lon = 2;
|
||||
double lat = 3;
|
||||
string name = 4;
|
||||
string houseNumber = 5;
|
||||
string street = 6;
|
||||
|
|
|
@ -19,7 +19,7 @@ import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
|||
export class CreateAdGrpcController {
|
||||
constructor(private readonly commandBus: CommandBus) {}
|
||||
|
||||
@GrpcMethod('AdsService', 'Create')
|
||||
@GrpcMethod('AdService', 'Create')
|
||||
async create(data: CreateAdRequestDto): Promise<IdResponse> {
|
||||
try {
|
||||
const aggregateID: AggregateID = await this.commandBus.execute(
|
||||
|
|
|
@ -1,17 +1,9 @@
|
|||
import { Transform } from 'class-transformer';
|
||||
import { IsLatitude, IsLongitude } from 'class-validator';
|
||||
import { toPrecision } from './transformers/to-precision';
|
||||
|
||||
export class CoordinatesDto {
|
||||
@Transform(({ value }) => toPrecision(value, 6), {
|
||||
toClassOnly: true,
|
||||
})
|
||||
@IsLongitude()
|
||||
lon: number;
|
||||
|
||||
@Transform(({ value }) => toPrecision(value, 6), {
|
||||
toClassOnly: true,
|
||||
})
|
||||
@IsLatitude()
|
||||
lat: number;
|
||||
}
|
||||
|
|
|
@ -9,14 +9,13 @@ import {
|
|||
IsArray,
|
||||
IsISO8601,
|
||||
} from 'class-validator';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { ScheduleDto } from './schedule.dto';
|
||||
import { MarginDurationsDto } from './margin-durations.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ScheduleItemDto } from './schedule-item.dto';
|
||||
import { WaypointDto } from './waypoint.dto';
|
||||
import { intToFrequency } from './transformers/int-to-frequency';
|
||||
import { IsSchedule } from './validators/decorators/is-schedule.decorator';
|
||||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
||||
import { HasDay } from './validators/decorators/has-day.decorator';
|
||||
|
||||
export class CreateAdRequestDto {
|
||||
@IsUUID(4)
|
||||
|
@ -30,10 +29,10 @@ export class CreateAdRequestDto {
|
|||
@IsBoolean()
|
||||
passenger?: boolean;
|
||||
|
||||
@Transform(({ value }) => intToFrequency(value), {
|
||||
toClassOnly: true,
|
||||
})
|
||||
@IsEnum(Frequency)
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
@IsISO8601({
|
||||
|
@ -46,17 +45,16 @@ export class CreateAdRequestDto {
|
|||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
@IsAfterOrEqual('fromDate', {
|
||||
message: 'toDate must be after or equal to fromDate',
|
||||
})
|
||||
toDate: string;
|
||||
|
||||
@Type(() => ScheduleDto)
|
||||
@IsSchedule()
|
||||
@Type(() => ScheduleItemDto)
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
schedule: ScheduleDto;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => MarginDurationsDto)
|
||||
@ValidateNested({ each: true })
|
||||
marginDurations?: MarginDurationsDto;
|
||||
schedule: ScheduleItemDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import { IsInt, IsOptional } from 'class-validator';
|
||||
|
||||
export class MarginDurationsDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
mon?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
tue?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
wed?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
thu?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
fri?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sat?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sun?: number;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { IsOptional, IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
||||
|
||||
export class ScheduleItemDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
day?: number;
|
||||
|
||||
@IsMilitaryTime()
|
||||
time: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
margin?: number;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
import { IsOptional, IsMilitaryTime } from 'class-validator';
|
||||
|
||||
export class ScheduleDto {
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
mon?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
tue?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
wed?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
thu?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
fri?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
sat?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsMilitaryTime()
|
||||
sun?: string;
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
|
||||
export const intToFrequency = (frequencyAsInt: number): Frequency => {
|
||||
if (frequencyAsInt == 1) return Frequency.PUNCTUAL;
|
||||
if (frequencyAsInt == 2) return Frequency.RECURRENT;
|
||||
throw new Error('Unknown frequency value');
|
||||
};
|
|
@ -1,4 +0,0 @@
|
|||
export const toPrecision = (input: number, precision: number): number => {
|
||||
const multiplier = 10 ** precision;
|
||||
return Math.round((input + Number.EPSILON) * multiplier) / multiplier;
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasDay(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasDay',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
value == Frequency.PUNCTUAL ||
|
||||
(Array.isArray(relatedValue) &&
|
||||
relatedValue.some((scheduleItem) =>
|
||||
scheduleItem.hasOwnProperty('day'),
|
||||
))
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
isISO8601,
|
||||
} from 'class-validator';
|
||||
|
||||
export function IsAfterOrEqual(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isAfterOrEqual',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
if (
|
||||
!(
|
||||
typeof value === 'string' &&
|
||||
typeof relatedValue === 'string' &&
|
||||
isISO8601(value, {
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
}) &&
|
||||
isISO8601(relatedValue, {
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
return false;
|
||||
return new Date(value) >= new Date(relatedValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import {
|
||||
ValidateBy,
|
||||
ValidationArguments,
|
||||
ValidationOptions,
|
||||
buildMessage,
|
||||
} from 'class-validator';
|
||||
|
||||
export const IsSchedule = (
|
||||
validationOptions?: ValidationOptions,
|
||||
): PropertyDecorator =>
|
||||
ValidateBy(
|
||||
{
|
||||
name: '',
|
||||
constraints: [],
|
||||
validator: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
validate: (value, args: ValidationArguments): boolean =>
|
||||
Object.keys(value).length > 0,
|
||||
defaultMessage: buildMessage(
|
||||
() => `schedule is invalid`,
|
||||
validationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
|
@ -23,7 +23,7 @@ export class FindAdByIdGrpcController {
|
|||
private readonly queryBus: QueryBus,
|
||||
) {}
|
||||
|
||||
@GrpcMethod('AdsService', 'FindOneById')
|
||||
@GrpcMethod('AdService', 'FindOneById')
|
||||
async findOnebyId(data: FindAdByIdRequestDto): Promise<AdResponseDto> {
|
||||
try {
|
||||
const ad: AdEntity = await this.queryBus.execute(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
|
@ -13,7 +13,7 @@ import {
|
|||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
|
||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
|
||||
import { TimeConverter } from '@modules/ad/infrastructure/time-converter';
|
||||
import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder';
|
||||
|
@ -39,6 +39,9 @@ describe('Ad Repository', () => {
|
|||
const baseUuid = {
|
||||
uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00',
|
||||
};
|
||||
const baseScheduleUuid = {
|
||||
uuid: 'bad5e786-3b15-4e51-a8fc-926fa9327ff1',
|
||||
};
|
||||
const baseOriginWaypointUuid = {
|
||||
uuid: 'bad5e786-3b15-4e51-a8fc-926fa9327ff1',
|
||||
};
|
||||
|
@ -59,20 +62,11 @@ describe('Ad Repository', () => {
|
|||
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:00:00Z'`,
|
||||
monMargin: 900,
|
||||
tueMargin: 900,
|
||||
wedMargin: 900,
|
||||
thuMargin: 900,
|
||||
friMargin: 900,
|
||||
satMargin: 900,
|
||||
sunMargin: 900,
|
||||
};
|
||||
const schedulePunctualAd = {
|
||||
day: 0,
|
||||
time: `'07:00'`,
|
||||
margin: 900,
|
||||
};
|
||||
const originWaypoint = {
|
||||
position: 0,
|
||||
|
@ -101,6 +95,11 @@ describe('Ad Repository', () => {
|
|||
for (let i = 0; i < nbToCreate; i++) {
|
||||
adToCreate.uuid = getSeed(i, baseUuid.uuid);
|
||||
await executeInsertCommand('ad', adToCreate);
|
||||
await executeInsertCommand('schedule_item', {
|
||||
uuid: getSeed(i, baseScheduleUuid.uuid),
|
||||
adUuid: adToCreate.uuid,
|
||||
...schedulePunctualAd,
|
||||
});
|
||||
await executeInsertCommand('waypoint', {
|
||||
uuid: getSeed(i, baseOriginWaypointUuid.uuid),
|
||||
adUuid: adToCreate.uuid,
|
||||
|
@ -138,8 +137,8 @@ describe('Ad Repository', () => {
|
|||
useClass: AdRepository,
|
||||
},
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useClass: DefaultParamsProvider,
|
||||
provide: AD_MESSAGE_PUBLISHER,
|
||||
useValue: mockMessagePublisher,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
|
@ -150,8 +149,8 @@ describe('Ad Repository', () => {
|
|||
useClass: TimeConverter,
|
||||
},
|
||||
{
|
||||
provide: AD_MESSAGE_PUBLISHER,
|
||||
useValue: mockMessagePublisher,
|
||||
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||
useClass: OutputDateTimeTransformer,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
@ -176,6 +175,7 @@ describe('Ad Repository', () => {
|
|||
await createPunctualDriverAds(1);
|
||||
const result = await adRepository.findOneById(baseUuid.uuid, {
|
||||
waypoints: true,
|
||||
schedule: true,
|
||||
});
|
||||
|
||||
expect(result.id).toBe(baseUuid.uuid);
|
||||
|
@ -183,7 +183,7 @@ describe('Ad Repository', () => {
|
|||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create an ad', async () => {
|
||||
it('should create a punctual ad', async () => {
|
||||
const beforeCount = await prismaService.ad.count();
|
||||
|
||||
const createAdProps: CreateAdProps = {
|
||||
|
@ -193,12 +193,13 @@ describe('Ad Repository', () => {
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: '2023-02-01',
|
||||
toDate: '2023-02-01',
|
||||
schedule: {
|
||||
wed: '12:05',
|
||||
},
|
||||
marginDurations: {
|
||||
wed: 900,
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '12:05',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
|
@ -233,15 +234,7 @@ describe('Ad Repository', () => {
|
|||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDurations: {
|
||||
mon: 900,
|
||||
tue: 900,
|
||||
wed: 900,
|
||||
thu: 900,
|
||||
fri: 900,
|
||||
sat: 900,
|
||||
sun: 900,
|
||||
},
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
|
@ -258,19 +251,92 @@ describe('Ad Repository', () => {
|
|||
expect(afterCount - beforeCount).toBe(1);
|
||||
});
|
||||
|
||||
// it('should throw a UniqueConstraintException if ad already exists', async () => {
|
||||
// await prismaService.ad.create({
|
||||
// data: {
|
||||
// uuid: uuid,
|
||||
// password: bcrypt.hashSync(`password`, 10),
|
||||
// },
|
||||
// });
|
||||
it('should create a recurrent ad', async () => {
|
||||
const beforeCount = await prismaService.ad.count();
|
||||
|
||||
// const authenticationToCreate: AuthenticationEntity =
|
||||
// await AuthenticationEntity.create(createAuthenticationProps);
|
||||
// await expect(
|
||||
// authenticationRepository.insert(authenticationToCreate),
|
||||
// ).rejects.toBeInstanceOf(UniqueConstraintException);
|
||||
// });
|
||||
const createAdProps: CreateAdProps = {
|
||||
userId: 'b4b56444-f8d3-4110-917c-e37bba77f383',
|
||||
driver: true,
|
||||
passenger: false,
|
||||
frequency: Frequency.RECURRENT,
|
||||
fromDate: '2023-02-01',
|
||||
toDate: '2024-01-31',
|
||||
schedule: [
|
||||
{
|
||||
day: 1,
|
||||
time: '08:00',
|
||||
margin: 900,
|
||||
},
|
||||
{
|
||||
day: 2,
|
||||
time: '08:00',
|
||||
margin: 900,
|
||||
},
|
||||
{
|
||||
day: 3,
|
||||
time: '09:00',
|
||||
margin: 900,
|
||||
},
|
||||
{
|
||||
day: 4,
|
||||
time: '08:00',
|
||||
margin: 900,
|
||||
},
|
||||
{
|
||||
day: 5,
|
||||
time: '08:00',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
waypoints: [
|
||||
{
|
||||
position: 0,
|
||||
address: {
|
||||
locality: 'Nice',
|
||||
postalCode: '06000',
|
||||
country: 'France',
|
||||
coordinates: {
|
||||
lon: 43.7102,
|
||||
lat: 7.262,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
position: 1,
|
||||
address: {
|
||||
locality: 'Marseille',
|
||||
postalCode: '13000',
|
||||
country: 'France',
|
||||
coordinates: {
|
||||
lon: 43.2965,
|
||||
lat: 5.3698,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const adToCreate: AdEntity = AdEntity.create(
|
||||
createAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
await adRepository.insert(adToCreate);
|
||||
|
||||
const afterCount = await prismaService.ad.count();
|
||||
|
||||
expect(afterCount - beforeCount).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
|
||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import {
|
||||
AdReadModel,
|
||||
AdWriteModel,
|
||||
|
@ -26,15 +20,13 @@ const adEntity: AdEntity = new AdEntity({
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: {
|
||||
mon: '07:15',
|
||||
tue: '07:15',
|
||||
wed: '07:15',
|
||||
thu: '07:15',
|
||||
fri: '07:15',
|
||||
sat: '07:15',
|
||||
sun: '07:15',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '07:15',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
waypoints: [
|
||||
{
|
||||
position: 0,
|
||||
|
@ -63,15 +55,6 @@ const adEntity: AdEntity = new AdEntity({
|
|||
},
|
||||
},
|
||||
],
|
||||
marginDurations: {
|
||||
mon: 600,
|
||||
tue: 600,
|
||||
wed: 600,
|
||||
thu: 600,
|
||||
fri: 600,
|
||||
sat: 600,
|
||||
sun: 600,
|
||||
},
|
||||
strict: false,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
|
@ -87,13 +70,16 @@ const adReadModel: AdReadModel = {
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: new Date('2023-06-21'),
|
||||
toDate: new Date('2023-06-21'),
|
||||
monTime: undefined,
|
||||
tueTime: undefined,
|
||||
wedTime: new Date('2023-06-21T07:15:00Z'),
|
||||
thuTime: undefined,
|
||||
friTime: undefined,
|
||||
satTime: undefined,
|
||||
sunTime: undefined,
|
||||
schedule: [
|
||||
{
|
||||
uuid: '3978f3d6-560f-4a8f-83ba-9bf5aa9a2d27',
|
||||
day: 3,
|
||||
time: new Date('2023-06-21T07:05:00Z'),
|
||||
margin: 900,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
],
|
||||
waypoints: [
|
||||
{
|
||||
uuid: '6f53f55e-2bdb-4c23-b6a9-6d7b498e47b9',
|
||||
|
@ -120,13 +106,6 @@ const adReadModel: AdReadModel = {
|
|||
updatedAt: now,
|
||||
},
|
||||
],
|
||||
monMargin: 600,
|
||||
tueMargin: 600,
|
||||
wedMargin: 600,
|
||||
thuMargin: 600,
|
||||
friMargin: 600,
|
||||
satMargin: 600,
|
||||
sunMargin: 600,
|
||||
strict: false,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
|
@ -134,42 +113,11 @@ const adReadModel: AdReadModel = {
|
|||
updatedAt: now,
|
||||
};
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
MON_MARGIN: 900,
|
||||
TUE_MARGIN: 900,
|
||||
WED_MARGIN: 900,
|
||||
THU_MARGIN: 900,
|
||||
FRI_MARGIN: 900,
|
||||
SAT_MARGIN: 900,
|
||||
SUN_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
timezones: jest.fn().mockImplementation((lon: number, lat: number) => {
|
||||
if (lon < 60) return 'Europe/Paris';
|
||||
return 'America/New_York';
|
||||
}),
|
||||
};
|
||||
|
||||
const mockTimeConverter: TimeConverterPort = {
|
||||
localDateTimeToUtc: jest
|
||||
.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
|
||||
return datetime;
|
||||
}),
|
||||
utcDatetimeToLocalTime: jest.fn(),
|
||||
const mockOutputDatetimeTransformer: DateTimeTransformerPort = {
|
||||
fromDate: jest.fn(),
|
||||
toDate: jest.fn(),
|
||||
day: jest.fn(),
|
||||
time: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Ad Mapper', () => {
|
||||
|
@ -180,16 +128,8 @@ describe('Ad Mapper', () => {
|
|||
providers: [
|
||||
AdMapper,
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
},
|
||||
{
|
||||
provide: TIME_CONVERTER,
|
||||
useValue: mockTimeConverter,
|
||||
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||
useValue: mockOutputDatetimeTransformer,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
@ -204,6 +144,7 @@ describe('Ad Mapper', () => {
|
|||
const mapped: AdWriteModel = adMapper.toPersistence(adEntity);
|
||||
expect(mapped.waypoints.create[0].uuid.length).toBe(36);
|
||||
expect(mapped.waypoints.create[1].uuid.length).toBe(36);
|
||||
expect(mapped.schedule.create.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should map persisted data to domain entity', async () => {
|
||||
|
@ -212,6 +153,8 @@ describe('Ad Mapper', () => {
|
|||
48.689445,
|
||||
);
|
||||
expect(mapped.getProps().waypoints[1].address.coordinates.lon).toBe(2.3522);
|
||||
expect(mapped.getProps().schedule.length).toBe(1);
|
||||
expect(mapped.getProps().schedule[0].time).toBe('07:05');
|
||||
});
|
||||
|
||||
it('should map domain entity to response', async () => {
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { MarginDurationsProps } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
|
||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||
|
||||
const originWaypointProps: WaypointProps = {
|
||||
|
@ -33,15 +32,7 @@ const destinationWaypointProps: WaypointProps = {
|
|||
},
|
||||
},
|
||||
};
|
||||
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,
|
||||
|
@ -52,77 +43,86 @@ const baseCreateAdProps = {
|
|||
const punctualCreateAdProps = {
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: {
|
||||
wed: '08:30',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
};
|
||||
const recurrentCreateAdProps = {
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2024-06-20',
|
||||
schedule: {
|
||||
mon: '08:30',
|
||||
tue: '08:30',
|
||||
wed: '08:00',
|
||||
thu: '08:30',
|
||||
fri: '08:30',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
day: 1,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 2,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 3,
|
||||
time: '08:00',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 4,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
{
|
||||
day: 5,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.RECURRENT,
|
||||
};
|
||||
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const recurrentPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const punctualDriverCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const recurrentDriverCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualDriverPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const recurrentDriverPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...recurrentCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDurations: {
|
||||
mon: 900,
|
||||
tue: 900,
|
||||
wed: 900,
|
||||
thu: 900,
|
||||
fri: 900,
|
||||
sat: 900,
|
||||
sun: 900,
|
||||
},
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
|
@ -136,8 +136,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualPassengerAd.id.length).toBe(36);
|
||||
expect(punctualPassengerAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualPassengerAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualPassengerAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualPassengerAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualPassengerAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -147,8 +148,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverAd.id.length).toBe(36);
|
||||
expect(punctualDriverAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualDriverAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualDriverAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualDriverAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualDriverAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualDriverAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualDriverAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
|
@ -158,8 +160,11 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule[0].day).toBe(3);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualDriverPassengerAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -169,8 +174,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentPassengerAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(recurrentPassengerAd.getProps().schedule.sat).toBeUndefined();
|
||||
expect(recurrentPassengerAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentPassengerAd.getProps().schedule[0].day).toBe(1);
|
||||
expect(recurrentPassengerAd.getProps().schedule[2].time).toBe('08:00');
|
||||
expect(recurrentPassengerAd.getProps().driver).toBeFalsy();
|
||||
expect(recurrentPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -180,8 +186,9 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverAd.id.length).toBe(36);
|
||||
expect(recurrentDriverAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(recurrentDriverAd.getProps().schedule.sat).toBeUndefined();
|
||||
expect(recurrentDriverAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentDriverAd.getProps().schedule[1].day).toBe(2);
|
||||
expect(recurrentDriverAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(recurrentDriverAd.getProps().driver).toBeTruthy();
|
||||
expect(recurrentDriverAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
|
@ -191,10 +198,11 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.mon).toBe('08:30');
|
||||
expect(
|
||||
recurrentDriverPassengerAd.getProps().schedule.sat,
|
||||
).toBeUndefined();
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule[3].day).toBe(4);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule[4].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.getProps().driver).toBeTruthy();
|
||||
expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -205,7 +213,6 @@ describe('Ad entity create', () => {
|
|||
const punctualWithoutRoleCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -214,8 +221,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutRoleAd.id.length).toBe(36);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutRoleAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutRoleAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
|
@ -227,7 +234,6 @@ describe('Ad entity create', () => {
|
|||
strict: undefined,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
|
@ -236,8 +242,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutStrictAd.id.length).toBe(36);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.mon).toBeUndefined();
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutStrictAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutStrictAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutStrictAd.getProps().strict).toBeFalsy();
|
||||
|
@ -250,7 +256,6 @@ describe('Ad entity create', () => {
|
|||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
|
@ -259,10 +264,10 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutSeatsRequestedAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().driver).toBeFalsy();
|
||||
|
@ -277,7 +282,6 @@ describe('Ad entity create', () => {
|
|||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -286,10 +290,8 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutSeatsProposedAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().driver).toBeTruthy();
|
||||
|
@ -297,56 +299,29 @@ describe('Ad entity create', () => {
|
|||
expect(punctualWithoutSeatsProposedAd.getProps().seatsProposed).toBe(3);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with margin durations if margin durations are empty', async () => {
|
||||
const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = {
|
||||
const punctualWithoutMarginDurationCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: {},
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationsCreateAdProps,
|
||||
const punctualWithoutMarginDurationAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe(
|
||||
expect(punctualWithoutMarginDurationAd.id.length).toBe(36);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy();
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().marginDurations.mon,
|
||||
).toBe(900);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with margin durations if margin durations are undefined', async () => {
|
||||
const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: undefined,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationsCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy();
|
||||
expect(
|
||||
punctualWithoutMarginDurationsAd.getProps().marginDurations.mon,
|
||||
punctualWithoutMarginDurationAd.getProps().schedule[0].margin,
|
||||
).toBe(900);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
it('should create a new punctual passenger ad entity with valid positions if positions are missing', async () => {
|
||||
const punctualWithoutPositionsCreateAdProps: CreateAdProps = {
|
||||
|
@ -383,7 +358,6 @@ describe('Ad entity create', () => {
|
|||
},
|
||||
],
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
|
@ -392,10 +366,10 @@ describe('Ad entity create', () => {
|
|||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.id.length).toBe(36);
|
||||
expect(
|
||||
punctualWithoutPositionsAd.getProps().schedule.mon,
|
||||
).toBeUndefined();
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule.wed).toBe('08:30');
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutPositionsAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutPositionsAd.getProps().waypoints[0].position).toBe(
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
||||
import { AggregateID } from '@mobicoop/ddd-library';
|
||||
|
@ -10,6 +14,7 @@ import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/de
|
|||
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
||||
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
|
||||
const originWaypoint: WaypointDto = {
|
||||
position: 0,
|
||||
|
@ -33,9 +38,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
|||
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
|
||||
fromDate: '2023-12-21',
|
||||
toDate: '2023-12-21',
|
||||
schedule: {
|
||||
thu: '08:15',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
},
|
||||
],
|
||||
driver: true,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
|
@ -58,13 +65,7 @@ const mockAdRepository = {
|
|||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
MON_MARGIN: 900,
|
||||
TUE_MARGIN: 900,
|
||||
WED_MARGIN: 900,
|
||||
THU_MARGIN: 900,
|
||||
FRI_MARGIN: 900,
|
||||
SAT_MARGIN: 900,
|
||||
SUN_MARGIN: 900,
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
|
@ -75,6 +76,13 @@ const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
|||
},
|
||||
};
|
||||
|
||||
const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
||||
fromDate: jest.fn(),
|
||||
toDate: jest.fn(),
|
||||
day: jest.fn(),
|
||||
time: jest.fn(),
|
||||
};
|
||||
|
||||
describe('create-ad.service', () => {
|
||||
let createAdService: CreateAdService;
|
||||
|
||||
|
@ -89,6 +97,10 @@ describe('create-ad.service', () => {
|
|||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: INPUT_DATETIME_TRANSFORMER,
|
||||
useValue: mockInputDateTimeTransformer,
|
||||
},
|
||||
CreateAdService,
|
||||
],
|
||||
}).compile();
|
||||
|
@ -102,7 +114,7 @@ describe('create-ad.service', () => {
|
|||
|
||||
describe('execution', () => {
|
||||
const createAdCommand = new CreateAdCommand(punctualCreateAdRequest);
|
||||
it('should create a new ad', async () => {
|
||||
it('should create a new punctual ad', async () => {
|
||||
AdEntity.create = jest.fn().mockReturnValue({
|
||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query';
|
||||
import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
|
||||
import { MarginDurationsProps } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
|
||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
|
@ -37,15 +36,6 @@ const destinationWaypointProps: WaypointProps = {
|
|||
},
|
||||
},
|
||||
};
|
||||
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,
|
||||
|
@ -56,31 +46,24 @@ const baseCreateAdProps = {
|
|||
const punctualCreateAdProps = {
|
||||
fromDate: '2023-06-22',
|
||||
toDate: '2023-06-22',
|
||||
schedule: {
|
||||
wed: '08:30',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
time: '08:30',
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
};
|
||||
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
marginDurations: marginDurationsProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
marginDuration: 900,
|
||||
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,
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import { MarginDurations } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
|
||||
|
||||
describe('Margin durations value object', () => {
|
||||
it('should create a margin durations value object', () => {
|
||||
const marginDurationsVO = new MarginDurations({
|
||||
mon: 600,
|
||||
tue: 610,
|
||||
wed: 620,
|
||||
thu: 630,
|
||||
fri: 640,
|
||||
sat: 650,
|
||||
sun: 660,
|
||||
});
|
||||
expect(marginDurationsVO.mon).toBe(600);
|
||||
expect(marginDurationsVO.tue).toBe(610);
|
||||
expect(marginDurationsVO.wed).toBe(620);
|
||||
expect(marginDurationsVO.thu).toBe(630);
|
||||
expect(marginDurationsVO.fri).toBe(640);
|
||||
expect(marginDurationsVO.sat).toBe(650);
|
||||
expect(marginDurationsVO.sun).toBe(660);
|
||||
});
|
||||
it('should update margin durations value object values', () => {
|
||||
const marginDurationsVO = new MarginDurations({
|
||||
mon: 600,
|
||||
tue: 610,
|
||||
wed: 620,
|
||||
thu: 630,
|
||||
fri: 640,
|
||||
sat: 650,
|
||||
sun: 660,
|
||||
});
|
||||
marginDurationsVO.mon = 700;
|
||||
marginDurationsVO.tue = 710;
|
||||
marginDurationsVO.wed = 720;
|
||||
marginDurationsVO.thu = 730;
|
||||
marginDurationsVO.fri = 740;
|
||||
marginDurationsVO.sat = 750;
|
||||
marginDurationsVO.sun = 760;
|
||||
expect(marginDurationsVO.mon).toBe(700);
|
||||
expect(marginDurationsVO.tue).toBe(710);
|
||||
expect(marginDurationsVO.wed).toBe(720);
|
||||
expect(marginDurationsVO.thu).toBe(730);
|
||||
expect(marginDurationsVO.fri).toBe(740);
|
||||
expect(marginDurationsVO.sat).toBe(750);
|
||||
expect(marginDurationsVO.sun).toBe(760);
|
||||
});
|
||||
});
|
|
@ -39,20 +39,13 @@ describe('Publish message when ad is created domain event handler', () => {
|
|||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: '2023-06-28',
|
||||
toDate: '2023-06-28',
|
||||
monTime: undefined,
|
||||
tueTime: undefined,
|
||||
wedTime: '07:15',
|
||||
thuTime: undefined,
|
||||
friTime: undefined,
|
||||
satTime: undefined,
|
||||
sunTime: undefined,
|
||||
monMarginDuration: 900,
|
||||
tueMarginDuration: 900,
|
||||
wedMarginDuration: 900,
|
||||
thuMarginDuration: 900,
|
||||
friMarginDuration: 900,
|
||||
satMarginDuration: 900,
|
||||
sunMarginDuration: 900,
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '07:15',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
|
@ -88,7 +81,7 @@ describe('Publish message when ad is created domain event handler', () => {
|
|||
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
||||
expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
|
||||
'ad.created',
|
||||
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
|
||||
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","schedule":[{"day":3,"time":"07:15","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { ScheduleItem } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
|
||||
|
||||
describe('Schedule item value object', () => {
|
||||
it('should create a schedule item value object', () => {
|
||||
const scheduleItemVO = new ScheduleItem({
|
||||
day: 0,
|
||||
time: '07:00',
|
||||
margin: 900,
|
||||
});
|
||||
expect(scheduleItemVO.day).toBe(0);
|
||||
expect(scheduleItemVO.time).toBe('07:00');
|
||||
expect(scheduleItemVO.margin).toBe(900);
|
||||
});
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
import { Schedule } from '@modules/ad/core/domain/value-objects/schedule.value-object';
|
||||
|
||||
describe('Schedule value object', () => {
|
||||
it('should create a schedule value object', () => {
|
||||
const scheduleVO = new Schedule({
|
||||
mon: '07:00',
|
||||
tue: '07:05',
|
||||
wed: '07:10',
|
||||
thu: '07:15',
|
||||
fri: '07:20',
|
||||
sat: '07:25',
|
||||
sun: '07:30',
|
||||
});
|
||||
expect(scheduleVO.mon).toBe('07:00');
|
||||
expect(scheduleVO.tue).toBe('07:05');
|
||||
expect(scheduleVO.wed).toBe('07:10');
|
||||
expect(scheduleVO.thu).toBe('07:15');
|
||||
expect(scheduleVO.fri).toBe('07:20');
|
||||
expect(scheduleVO.sat).toBe('07:25');
|
||||
expect(scheduleVO.sun).toBe('07:30');
|
||||
});
|
||||
});
|
|
@ -1,59 +1,22 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
|
||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||
import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
|
||||
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
MON_MARGIN: 900,
|
||||
TUE_MARGIN: 900,
|
||||
WED_MARGIN: 900,
|
||||
THU_MARGIN: 900,
|
||||
FRI_MARGIN: 900,
|
||||
SAT_MARGIN: 900,
|
||||
SUN_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
timezones: jest.fn().mockImplementation((lon: number, lat: number) => {
|
||||
if (lon < 60) return 'Europe/Paris';
|
||||
return 'America/New_York';
|
||||
}),
|
||||
};
|
||||
|
||||
const mockTimeConverter: TimeConverterPort = {
|
||||
localDateTimeToUtc: jest
|
||||
.fn()
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
|
||||
return datetime;
|
||||
}),
|
||||
utcDatetimeToLocalTime: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessagePublisher = {
|
||||
publish: jest.fn().mockImplementation(),
|
||||
};
|
||||
|
||||
const mockOutputDatetimeTransformer: DateTimeTransformerPort = {
|
||||
fromDate: jest.fn(),
|
||||
toDate: jest.fn(),
|
||||
day: jest.fn(),
|
||||
time: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Ad repository', () => {
|
||||
let prismaService: PrismaService;
|
||||
let adMapper: AdMapper;
|
||||
|
@ -66,16 +29,8 @@ describe('Ad repository', () => {
|
|||
PrismaService,
|
||||
AdMapper,
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
},
|
||||
{
|
||||
provide: TIME_CONVERTER,
|
||||
useValue: mockTimeConverter,
|
||||
provide: OUTPUT_DATETIME_TRANSFORMER,
|
||||
useValue: mockOutputDatetimeTransformer,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||
const mockConfigService = {
|
||||
get: jest.fn().mockImplementation((value: string) => {
|
||||
switch (value) {
|
||||
case 'DEPARTURE_MARGIN':
|
||||
case 'DEPARTURE_TIME_MARGIN':
|
||||
return 900;
|
||||
case 'ROLE':
|
||||
return 'passenger';
|
||||
|
@ -50,7 +50,7 @@ describe('DefaultParamsProvider', () => {
|
|||
|
||||
it('should provide default params', async () => {
|
||||
const params: DefaultParams = defaultParamsProvider.getParams();
|
||||
expect(params.SUN_MARGIN).toBe(900);
|
||||
expect(params.DEPARTURE_TIME_MARGIN).toBe(900);
|
||||
expect(params.PASSENGER).toBeTruthy();
|
||||
expect(params.DRIVER).toBeFalsy();
|
||||
expect(params.DEFAULT_TIMEZONE).toBe('Europe/Paris');
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
|
||||
const mockTimeConverter: TimeConverterPort = {
|
||||
localStringTimeToUtcStringTime: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => '00:15'),
|
||||
utcStringTimeToLocalStringTime: jest.fn(),
|
||||
localStringDateTimeToUtcDate: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => new Date('2023-07-30T06:15:00.000Z'))
|
||||
.mockImplementationOnce(() => new Date('2023-07-20T08:15:00.000Z'))
|
||||
.mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z'))
|
||||
.mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z')),
|
||||
utcStringDateTimeToLocalIsoString: jest.fn(),
|
||||
utcUnixEpochDayFromTime: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => 4)
|
||||
.mockImplementationOnce(() => 3)
|
||||
.mockImplementationOnce(() => 3)
|
||||
.mockImplementationOnce(() => 5)
|
||||
.mockImplementationOnce(() => 5),
|
||||
localUnixEpochDayFromTime: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Input Datetime Transformer', () => {
|
||||
let inputDatetimeTransformer: InputDateTimeTransformer;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
},
|
||||
{
|
||||
provide: TIME_CONVERTER,
|
||||
useValue: mockTimeConverter,
|
||||
},
|
||||
InputDateTimeTransformer,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
inputDatetimeTransformer = module.get<InputDateTimeTransformer>(
|
||||
InputDateTimeTransformer,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(inputDatetimeTransformer).toBeDefined();
|
||||
});
|
||||
|
||||
describe('fromDate', () => {
|
||||
it('should return fromDate as is if frequency is recurrent', () => {
|
||||
const transformedFromDate: string = inputDatetimeTransformer.fromDate(
|
||||
{
|
||||
date: '2023-07-30',
|
||||
time: '07:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(transformedFromDate).toBe('2023-07-30');
|
||||
});
|
||||
it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => {
|
||||
const transformedFromDate: string = inputDatetimeTransformer.fromDate(
|
||||
{
|
||||
date: '2023-07-30',
|
||||
time: '07:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(transformedFromDate).toBe('2023-07-30');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toDate', () => {
|
||||
it('should return toDate as is if frequency is recurrent', () => {
|
||||
const transformedToDate: string = inputDatetimeTransformer.toDate(
|
||||
'2024-07-29',
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '10:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(transformedToDate).toBe('2024-07-29');
|
||||
});
|
||||
it('should return transformed fromDate if frequency is punctual', () => {
|
||||
const transformedToDate: string = inputDatetimeTransformer.toDate(
|
||||
'2024-07-30',
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '10:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(transformedToDate).toBe('2023-07-20');
|
||||
});
|
||||
});
|
||||
|
||||
describe('day', () => {
|
||||
it('should not change day if frequency is recurrent and converted UTC time is on the same day', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '01:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(1);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted UTC time is on the previous day', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(0);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted UTC time is on the previous day and given day is sunday', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
0,
|
||||
{
|
||||
date: '2023-07-23',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(6);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted UTC time is on the next day', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 30.82,
|
||||
lat: 49.37,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(2);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted UTC time is on the next day and given day is saturday(6)', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
6,
|
||||
{
|
||||
date: '2023-07-29',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 30.82,
|
||||
lat: 49.37,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(0);
|
||||
});
|
||||
it('should return utc fromDate day if frequency is punctual', () => {
|
||||
const day: number = inputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(day).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('time', () => {
|
||||
it('should transform given time to utc time if frequency is recurrent', () => {
|
||||
const time: string = inputDatetimeTransformer.time(
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '01:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(time).toBe('00:15');
|
||||
});
|
||||
it('should return given time to utc time if frequency is punctual', () => {
|
||||
const time: string = inputDatetimeTransformer.time(
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '01:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(time).toBe('23:15');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,271 @@
|
|||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
DEFAULT_TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
|
||||
const mockTimeConverter: TimeConverterPort = {
|
||||
localStringTimeToUtcStringTime: jest.fn(),
|
||||
utcStringTimeToLocalStringTime: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => '00:15'),
|
||||
localStringDateTimeToUtcDate: jest.fn(),
|
||||
utcStringDateTimeToLocalIsoString: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => '2023-07-30T08:15:00.000+02:00')
|
||||
.mockImplementationOnce(() => '2023-07-20T10:15:00.000+02:00')
|
||||
.mockImplementationOnce(() => '2023-07-19T23:15:00.000+02:00')
|
||||
.mockImplementationOnce(() => '2023-07-20T00:15:00.000+02:00'),
|
||||
utcUnixEpochDayFromTime: jest.fn(),
|
||||
localUnixEpochDayFromTime: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => 4)
|
||||
.mockImplementationOnce(() => 5)
|
||||
.mockImplementationOnce(() => 5)
|
||||
.mockImplementationOnce(() => 3)
|
||||
.mockImplementationOnce(() => 3),
|
||||
};
|
||||
|
||||
describe('Output Datetime Transformer', () => {
|
||||
let outputDatetimeTransformer: OutputDateTimeTransformer;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
},
|
||||
{
|
||||
provide: TIME_CONVERTER,
|
||||
useValue: mockTimeConverter,
|
||||
},
|
||||
OutputDateTimeTransformer,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
outputDatetimeTransformer = module.get<OutputDateTimeTransformer>(
|
||||
OutputDateTimeTransformer,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(outputDatetimeTransformer).toBeDefined();
|
||||
});
|
||||
|
||||
describe('fromDate', () => {
|
||||
it('should return fromDate as is if frequency is recurrent', () => {
|
||||
const transformedFromDate: string = outputDatetimeTransformer.fromDate(
|
||||
{
|
||||
date: '2023-07-30',
|
||||
time: '07:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(transformedFromDate).toBe('2023-07-30');
|
||||
});
|
||||
it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => {
|
||||
const transformedFromDate: string = outputDatetimeTransformer.fromDate(
|
||||
{
|
||||
date: '2023-07-30',
|
||||
time: '07:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(transformedFromDate).toBe('2023-07-30');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toDate', () => {
|
||||
it('should return toDate as is if frequency is recurrent', () => {
|
||||
const transformedToDate: string = outputDatetimeTransformer.toDate(
|
||||
'2024-07-29',
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '10:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(transformedToDate).toBe('2024-07-29');
|
||||
});
|
||||
it('should return transformed fromDate if frequency is punctual', () => {
|
||||
const transformedToDate: string = outputDatetimeTransformer.toDate(
|
||||
'2024-07-30',
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '08:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(transformedToDate).toBe('2023-07-20');
|
||||
});
|
||||
});
|
||||
|
||||
describe('day', () => {
|
||||
it('should not change day if frequency is recurrent and converted local time is on the same day', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-24',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(1);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted local time is on the next day', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
0,
|
||||
{
|
||||
date: '2023-07-23',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(1);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted local time is on the next day and given day is saturday', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
6,
|
||||
{
|
||||
date: '2023-07-23',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(0);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted local time is on the previous day', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-25',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 30.82,
|
||||
lat: 49.37,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(0);
|
||||
});
|
||||
it('should change day if frequency is recurrent and converted local time is on the previous day and given day is sunday(0)', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
0,
|
||||
{
|
||||
date: '2023-07-30',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 30.82,
|
||||
lat: 49.37,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(day).toBe(6);
|
||||
});
|
||||
it('should return local fromDate day if frequency is punctual', () => {
|
||||
const day: number = outputDatetimeTransformer.day(
|
||||
1,
|
||||
{
|
||||
date: '2023-07-20',
|
||||
time: '00:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(day).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('time', () => {
|
||||
it('should transform utc time to local time if frequency is recurrent', () => {
|
||||
const time: string = outputDatetimeTransformer.time(
|
||||
{
|
||||
date: '2023-07-23',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.RECURRENT,
|
||||
);
|
||||
expect(time).toBe('00:15');
|
||||
});
|
||||
it('should return local time if frequency is punctual', () => {
|
||||
const time: string = outputDatetimeTransformer.time(
|
||||
{
|
||||
date: '2023-07-19',
|
||||
time: '23:15',
|
||||
coordinates: {
|
||||
lon: 6.175,
|
||||
lat: 48.685,
|
||||
},
|
||||
},
|
||||
Frequency.PUNCTUAL,
|
||||
);
|
||||
expect(time).toBe('00:15');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -6,35 +6,29 @@ describe('Time Converter', () => {
|
|||
expect(timeConverter).toBeDefined();
|
||||
});
|
||||
|
||||
describe('localDateTimeToUtc', () => {
|
||||
it('should convert a paris datetime to utc', () => {
|
||||
describe('localStringTimeToUtcStringTime', () => {
|
||||
it('should convert a paris time to utc time', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDatetime.toISOString()).toEqual('2023-06-22T06:00:00.000Z');
|
||||
expect(utcDatetime).toBe('07:00');
|
||||
});
|
||||
it('should return undefined if date is invalid', () => {
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-16-22';
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const parisTime = '28:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '28:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
const parisTime = undefined;
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
|
@ -42,55 +36,409 @@ describe('Time Converter', () => {
|
|||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
parisTime,
|
||||
const fooBarTime = '08:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
fooBarTime,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if date is undefined', () => {
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = undefined;
|
||||
const parisTime = '08:00';
|
||||
const utcDatetime = timeConverter.localDateTimeToUtc(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
const fooBarTime = '08:00';
|
||||
const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
|
||||
fooBarTime,
|
||||
undefined,
|
||||
);
|
||||
expect(utcDatetime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('utcDatetimeToLocalTime', () => {
|
||||
it('should convert an utc datetime isostring to a paris local time', () => {
|
||||
describe('utcStringTimeToLocalStringTime', () => {
|
||||
it('should convert a utc time to a paris time', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBe('08:25');
|
||||
expect(parisTime).toBe('08:00');
|
||||
});
|
||||
it('should return undefined if isostring input is invalid', () => {
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = 'not_an_isostring';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
const utcTime = '27:00';
|
||||
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone input is invalid', () => {
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z';
|
||||
const parisTime = timeConverter.utcDatetimeToLocalTime(
|
||||
utcDatetimeIsostring,
|
||||
const utcTime = undefined;
|
||||
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
|
||||
utcTime,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
|
||||
utcTime,
|
||||
undefined,
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('localStringDateTimeToUtcDate', () => {
|
||||
it('should convert a summer paris date and time to a utc date', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
|
||||
});
|
||||
it('should convert a winter paris date and time to a utc date', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-02-02';
|
||||
const parisTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
|
||||
});
|
||||
it('should convert a summer paris date and time to a utc date without dst', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
false,
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
|
||||
});
|
||||
it('should convert a tonga date and time to a utc date', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const tongaDate = '2023-02-02';
|
||||
const tongaTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
tongaDate,
|
||||
tongaTime,
|
||||
'Pacific/Tongatapu',
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-02-01T23:00:00.000Z');
|
||||
});
|
||||
it('should convert a papeete date and time to a utc date', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const papeeteDate = '2023-02-02';
|
||||
const papeeteTime = '15:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
papeeteDate,
|
||||
papeeteTime,
|
||||
'Pacific/Tahiti',
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-02-03T01:00:00.000Z');
|
||||
});
|
||||
it('should return undefined if date is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-32';
|
||||
const parisTime = '08:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if date is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = undefined;
|
||||
const parisTime = '08:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '28:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = undefined;
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const parisDate = '2023-06-22';
|
||||
const parisTime = '12:00';
|
||||
const utcDate = timeConverter.localStringDateTimeToUtcDate(
|
||||
parisDate,
|
||||
parisTime,
|
||||
undefined,
|
||||
);
|
||||
expect(utcDate).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('utcStringDateTimeToLocalIsoString', () => {
|
||||
it('should convert a utc string date and time to a summer paris date isostring', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = '10:00';
|
||||
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
|
||||
});
|
||||
it('should convert a utc string date and time to a winter paris date isostring', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-02-02';
|
||||
const utcTime = '10:00';
|
||||
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
|
||||
});
|
||||
it('should convert a utc string date and time to a summer paris date isostring without dst', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = '10:00';
|
||||
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
false,
|
||||
);
|
||||
expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00');
|
||||
});
|
||||
it('should convert a utc date to a tonga date isostring', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-02-01';
|
||||
const utcTime = '23:00';
|
||||
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Pacific/Tongatapu',
|
||||
);
|
||||
expect(localIsoString).toBe('2023-02-02T12:00:00.000+13:00');
|
||||
});
|
||||
it('should convert a utc date to a papeete date isostring', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-02-03';
|
||||
const utcTime = '01:00';
|
||||
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Pacific/Tahiti',
|
||||
);
|
||||
expect(localIsoString).toBe('2023-02-02T15:00:00.000-10:00');
|
||||
});
|
||||
it('should return undefined if date is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-32';
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if date is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = undefined;
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = '27:00';
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = undefined;
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Europe/Paris',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
'Foo/Bar',
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
const utcTime = '07:00';
|
||||
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
|
||||
utcDate,
|
||||
utcTime,
|
||||
undefined,
|
||||
);
|
||||
expect(parisTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('utcUnixEpochDayFromTime', () => {
|
||||
it('should get the utc day of paris at 12:00', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('12:00', 'Europe/Paris'),
|
||||
).toBe(4);
|
||||
});
|
||||
it('should get the utc day of paris at 00:00', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('00:00', 'Europe/Paris'),
|
||||
).toBe(3);
|
||||
});
|
||||
it('should get the utc day of papeete at 16:00', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('16:00', 'Pacific/Tahiti'),
|
||||
).toBe(5);
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('28:00', 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime(undefined, 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('12:00', 'Foo/Bar'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.utcUnixEpochDayFromTime('12:00', undefined),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('localUnixEpochDayFromTime', () => {
|
||||
it('should get the day of paris at 12:00 utc', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('12:00', 'Europe/Paris'),
|
||||
).toBe(4);
|
||||
});
|
||||
it('should get the day of paris at 23:00 utc', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('23:00', 'Europe/Paris'),
|
||||
).toBe(5);
|
||||
});
|
||||
it('should get the day of papeete at 05:00 utc', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('05:00', 'Pacific/Tahiti'),
|
||||
).toBe(3);
|
||||
});
|
||||
it('should return undefined if time is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('28:00', 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if time is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime(undefined, 'Europe/Paris'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is invalid', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('12:00', 'Foo/Bar'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
it('should return undefined if timezone is undefined', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
expect(
|
||||
timeConverter.localUnixEpochDayFromTime('12:00', undefined),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,9 +31,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
|||
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
|
||||
fromDate: '2023-12-21',
|
||||
toDate: '2023-12-21',
|
||||
schedule: {
|
||||
thu: '08:15',
|
||||
},
|
||||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
},
|
||||
],
|
||||
driver: false,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { ScheduleItemDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule-item.dto';
|
||||
import { HasDay } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-day.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('Has day decorator', () => {
|
||||
class MyClass {
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
schedule: ScheduleItemDto[];
|
||||
}
|
||||
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasDay = HasDay('someProperty');
|
||||
expect(typeof hasDay).toBe('function');
|
||||
});
|
||||
|
||||
it('should validate a punctual frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.PUNCTUAL;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should validate a recurrent frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
day: 1,
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not validate a recurrent frequency associated with an invalid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { intToFrequency } from '@modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency';
|
||||
|
||||
describe('frequency mapping', () => {
|
||||
it('should return punctual if frequency is 1', () => {
|
||||
expect(intToFrequency(1)).toBe(Frequency.PUNCTUAL);
|
||||
});
|
||||
it('should return recurrent if frequency is 2', () => {
|
||||
expect(intToFrequency(2)).toBe(Frequency.RECURRENT);
|
||||
});
|
||||
it('should throw an error if frequency is unknown', () => {
|
||||
expect(() => intToFrequency(0)).toThrow();
|
||||
expect(() => intToFrequency(3)).toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import { IsAfterOrEqual } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-after-or-equal.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('Is after or equal decorator', () => {
|
||||
class MyClass {
|
||||
firstDate: string;
|
||||
|
||||
@IsAfterOrEqual('firstDate', {
|
||||
message: 'secondDate must be after or equal to firstDate',
|
||||
})
|
||||
secondDate: string;
|
||||
}
|
||||
|
||||
it('should return a property decorator has a function', () => {
|
||||
const isAfterOrEqual = IsAfterOrEqual('someProperty');
|
||||
expect(typeof isAfterOrEqual).toBe('function');
|
||||
});
|
||||
|
||||
it('should validate a secondDate posterior to firstDate', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-20';
|
||||
myClassInstance.secondDate = '2023-07-30';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not validate a secondDate prior to firstDate', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-20';
|
||||
myClassInstance.secondDate = '2023-07-19';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not validate if dates are invalid', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-40';
|
||||
myClassInstance.secondDate = '2023-07-19';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1,30 +0,0 @@
|
|||
import { ScheduleDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule.dto';
|
||||
import { IsSchedule } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('schedule decorator', () => {
|
||||
class MyClass {
|
||||
@IsSchedule()
|
||||
schedule: ScheduleDto;
|
||||
}
|
||||
it('should return a property decorator has a function', () => {
|
||||
const isSchedule = IsSchedule();
|
||||
expect(typeof isSchedule).toBe('function');
|
||||
});
|
||||
it('should validate a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.schedule = {
|
||||
mon: '07:15',
|
||||
};
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should not validate a invalid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.schedule = {};
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -1,14 +0,0 @@
|
|||
import { toPrecision } from '@modules/ad/interface/grpc-controllers/dtos/transformers/to-precision';
|
||||
|
||||
describe('precision handler', () => {
|
||||
it('should return a 6 digits float number for a 10 digits float input number and 6 as precision', () => {
|
||||
const precised = toPrecision(1.1234567891, 6);
|
||||
const stringPrecised = precised.toString().split('.')[1];
|
||||
expect(stringPrecised.length).toBe(6);
|
||||
});
|
||||
it('should return a 2 digits float number for a 2 digits float input number and 4 as precision', () => {
|
||||
const precised = toPrecision(1.12, 4);
|
||||
const stringPrecised = precised.toString().split('.')[1];
|
||||
expect(stringPrecised.length).toBe(2);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue