mirror of
https://gitlab.com/mobicoop/v3/service/ad.git
synced 2026-01-10 10:12:40 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b342c049 | ||
|
|
da4b30350b | ||
|
|
55c7e2b11c | ||
|
|
c52afbb243 | ||
|
|
98d2b521ab | ||
|
|
bbb96cfd36 | ||
|
|
909ef04e69 | ||
|
|
540c63d297 | ||
|
|
c72c64e6da | ||
|
|
8f57dc2c7a | ||
|
|
41073539bf | ||
|
|
483e947d92 | ||
|
|
b13df86745 | ||
|
|
61c1d6ffcb | ||
|
|
fbc0ae2a33 | ||
|
|
b039dbb3bd | ||
|
|
2009355b18 | ||
|
|
5d6547a184 | ||
|
|
40e8b5f733 | ||
|
|
98068d021f | ||
|
|
4e236551ae | ||
|
|
4bd7ca64de | ||
|
|
3e1c4afce3 | ||
|
|
d4a37b237e | ||
|
|
b2cf66139a | ||
|
|
99017b0e55 | ||
|
|
ee0a2cb386 | ||
|
|
f69e8a95f1 | ||
|
|
976a3c3779 | ||
|
|
c85d6fb756 | ||
|
|
e0a4b07733 | ||
|
|
dfe4db8276 | ||
|
|
88a975a8a1 | ||
|
|
263133ec30 | ||
|
|
3d29eb4517 | ||
|
|
d3c305dbce | ||
|
|
4844f07e08 |
@@ -8,7 +8,7 @@ HEALTH_SERVICE_PORT=6006
|
|||||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=ad"
|
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=ad"
|
||||||
|
|
||||||
# MESSAGE BROKER
|
# MESSAGE BROKER
|
||||||
MESSAGE_BROKER_URI=amqp://v3-broker:5672
|
MESSAGE_BROKER_URI=amqp://mobicoop:mobicoop@v3-broker:5672
|
||||||
MESSAGE_BROKER_EXCHANGE=mobicoop
|
MESSAGE_BROKER_EXCHANGE=mobicoop
|
||||||
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
|
MESSAGE_BROKER_EXCHANGE_DURABILITY=true
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ stages:
|
|||||||
- test
|
- test
|
||||||
- build
|
- build
|
||||||
|
|
||||||
|
include:
|
||||||
|
- template: Security/SAST.gitlab-ci.yml
|
||||||
|
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# TEST STAGE #
|
# TEST STAGE #
|
||||||
##############
|
##############
|
||||||
|
|||||||
55
.gitlab/merge_request_templates/default.md
Normal file
55
.gitlab/merge_request_templates/default.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
_Replace italic text by your own description_
|
||||||
|
|
||||||
|
## Feature Merge Request
|
||||||
|
|
||||||
|
### Why this Merge Request
|
||||||
|
|
||||||
|
_This merge request addresses, and describe the problem or user story being addressed._
|
||||||
|
|
||||||
|
### What is implemented, what is the chosen solution
|
||||||
|
|
||||||
|
_Explain the fix or solution implemented. Which other solution have been envisaged._
|
||||||
|
|
||||||
|
### Related issues and impact on other project in codebase
|
||||||
|
|
||||||
|
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
|
||||||
|
|
||||||
|
_And Link to other project Impacted._
|
||||||
|
|
||||||
|
### Other Information
|
||||||
|
|
||||||
|
_Include any extra information or considerations for reviewers._
|
||||||
|
|
||||||
|
## Checklists
|
||||||
|
|
||||||
|
### Merge Request
|
||||||
|
|
||||||
|
- [ ] Target branch identified.
|
||||||
|
- [ ] Code based on last version of target branch.
|
||||||
|
- [ ] Description filled.
|
||||||
|
- [ ] Impact on other project codebase identified.
|
||||||
|
- [ ] Documentation reflects the changes made.
|
||||||
|
- [ ] Test run in gitlab pipeline and locally.
|
||||||
|
- [ ] One or more reviewer is defined
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
- [ ] Code follows project coding guidelines.
|
||||||
|
- [ ] Code follows project designed architecture.
|
||||||
|
- [ ] Code is easily readable.
|
||||||
|
- [ ] Everything new have an explicit and pertinent name (variable, method, file ...)
|
||||||
|
- [ ] No redundant/duplicate code (unless explain by architecture choice)
|
||||||
|
- [ ] Commit are all related to MR and well written (Atomic commit).
|
||||||
|
- [ ] New code is tested and covered by automated test.
|
||||||
|
- [ ] No useless logging or debugging code.
|
||||||
|
- [ ] No code can be replaced by library or framework code.
|
||||||
|
|
||||||
|
### TODO before merge
|
||||||
|
|
||||||
|
- [ ] _add any task here_
|
||||||
|
- [ ] ...
|
||||||
|
|
||||||
|
### TODO after merge
|
||||||
|
|
||||||
|
- [ ] _add any task here_
|
||||||
|
- [ ] ...
|
||||||
62
.gitlab/merge_request_templates/release.md
Normal file
62
.gitlab/merge_request_templates/release.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
_Replace italic text by your own description_
|
||||||
|
|
||||||
|
## Release Merge Request
|
||||||
|
|
||||||
|
### Why this Merge Request
|
||||||
|
|
||||||
|
_This merge request addresses, and describe the problem or user story being addressed._
|
||||||
|
|
||||||
|
### What is implemented, what is the chosen solution
|
||||||
|
|
||||||
|
_Explain the fix or solution implemented. Which other solution have been envisaged._
|
||||||
|
|
||||||
|
### Related issues and impact on other project in codebase
|
||||||
|
|
||||||
|
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
|
||||||
|
|
||||||
|
_And Link to other project Impacted._
|
||||||
|
|
||||||
|
### Other Information
|
||||||
|
|
||||||
|
_Include any extra information or considerations for reviewers._
|
||||||
|
|
||||||
|
## Checklists
|
||||||
|
|
||||||
|
### Merge Request
|
||||||
|
|
||||||
|
- [ ] Target branch identified.
|
||||||
|
- [ ] Code based on last version of target branch.
|
||||||
|
- [ ] Description filled.
|
||||||
|
- [ ] Impact on other project codebase identified.
|
||||||
|
- [ ] Documentation reflects the changes made.
|
||||||
|
- [ ] Test run in gitlab pipeline and locally.
|
||||||
|
- [ ] One or more reviewer is defined
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
- [ ] Code follows project coding guidelines.
|
||||||
|
- [ ] Code follows project designed architecture.
|
||||||
|
- [ ] Code is easily readable.
|
||||||
|
- [ ] Everything new have an explicit and pertinent name (variable, method, file ...)
|
||||||
|
- [ ] No redundant/duplicate code (unless explain by architecture choice)
|
||||||
|
- [ ] Commit are all related to MR and well written (Atomic commit).
|
||||||
|
- [ ] New code is tested and covered by automated test.
|
||||||
|
- [ ] No useless logging or debugging code.
|
||||||
|
- [ ] No code can be replaced by library or framework code.
|
||||||
|
|
||||||
|
### Change Management
|
||||||
|
|
||||||
|
- [ ] Release is planned
|
||||||
|
- [ ] Merge Request to be included are identified
|
||||||
|
- [ ] Concerned Team are aware of the change
|
||||||
|
- [ ] No other change on the same day (if possible)
|
||||||
|
|
||||||
|
### TODO before merge
|
||||||
|
|
||||||
|
- [ ] _add any task here_
|
||||||
|
- [ ] ...
|
||||||
|
|
||||||
|
### TODO after merge
|
||||||
|
|
||||||
|
- [ ] _add any task here_
|
||||||
|
- [ ] ...
|
||||||
37
.gitlab/merge_request_templates/smallfix.md
Normal file
37
.gitlab/merge_request_templates/smallfix.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
_Replace italic text by your own description_
|
||||||
|
|
||||||
|
## Small Fix Merge Request
|
||||||
|
|
||||||
|
### Why this Merge Request
|
||||||
|
|
||||||
|
_This merge request addresses, and describe the problem or user story being addressed._
|
||||||
|
|
||||||
|
### What is implemented, what is the chosen solution
|
||||||
|
|
||||||
|
_Explain the fix or solution implemented. Which other solution have been envisaged._
|
||||||
|
|
||||||
|
### Related issues and impact on other project in codebase
|
||||||
|
|
||||||
|
_Provide links to the related issues, feature requests and merge request (from Gitlab and Redmine)._
|
||||||
|
|
||||||
|
_And Link to other project Impacted._
|
||||||
|
|
||||||
|
### Other Information
|
||||||
|
|
||||||
|
_Include any extra information or considerations for reviewers._
|
||||||
|
|
||||||
|
## Checklists
|
||||||
|
|
||||||
|
### Merge Request
|
||||||
|
|
||||||
|
- [ ] Target branch identified.
|
||||||
|
- [ ] Code based on last version of target branch.
|
||||||
|
- [ ] Description filled.
|
||||||
|
- [ ] Impact on other project codebase identified.
|
||||||
|
- [ ] Test run in gitlab pipeline and locally.
|
||||||
|
|
||||||
|
### Code Review
|
||||||
|
|
||||||
|
- [ ] Code is easily readable.
|
||||||
|
- [ ] Commit are all related to MR and well written (Atomic commit).
|
||||||
|
- [ ] No useless logging or debugging code.
|
||||||
@@ -4,3 +4,4 @@ node_modules
|
|||||||
dist
|
dist
|
||||||
coverage
|
coverage
|
||||||
.prettierrc.json
|
.prettierrc.json
|
||||||
|
.gitlab
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# BUILD FOR LOCAL DEVELOPMENT
|
# BUILD FOR LOCAL DEVELOPMENT
|
||||||
###################
|
###################
|
||||||
|
|
||||||
FROM node:18-alpine3.16 As development
|
FROM docker.io/node:lts-hydrogen As development
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@@ -29,7 +29,7 @@ USER node
|
|||||||
# BUILD FOR PRODUCTION
|
# BUILD FOR PRODUCTION
|
||||||
###################
|
###################
|
||||||
|
|
||||||
FROM node:18-alpine3.16 As build
|
FROM docker.io/node:lts-hydrogen As build
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ USER node
|
|||||||
# PRODUCTION
|
# PRODUCTION
|
||||||
###################
|
###################
|
||||||
|
|
||||||
FROM node:18-alpine3.16 As production
|
FROM docker.io/node:lts-hydrogen As production
|
||||||
|
|
||||||
# Copy package.json to be able to execute migration command
|
# Copy package.json to be able to execute migration command
|
||||||
COPY --chown=node:node package*.json ./
|
COPY --chown=node:node package*.json ./
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -56,6 +56,26 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **FindAllByIds** : find all ads for the given ids
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ids": [
|
||||||
|
"80126a61-d128-4f96-afdb-92e33c75a3e1",
|
||||||
|
"80126a61-d128-4f96-afdb-92e33c75a3e2",
|
||||||
|
"80126a61-d128-4f96-afdb-92e33c75a3e3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **FindAllByUserId** : find all ads for the given user id
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "80c9bb02-0931-4a1d-bea6-22d358992245"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- **Create** : create an ad
|
- **Create** : create an ad
|
||||||
|
|
||||||
Punctual driver ad :
|
Punctual driver ad :
|
||||||
@@ -95,7 +115,8 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
"postalCode": "75000",
|
"postalCode": "75000",
|
||||||
"country": "France"
|
"country": "France"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"comment": "I'm flexible with the departure time"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -137,7 +158,8 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
"postalCode": "75000",
|
"postalCode": "75000",
|
||||||
"country": "France"
|
"country": "France"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"comment": "I'm flexible with the departure time"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -187,7 +209,8 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
"postalCode": "75000",
|
"postalCode": "75000",
|
||||||
"country": "France"
|
"country": "France"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"comment": "I'm flexible with the departure time"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -207,6 +230,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
- seatsRequested: number of seats requested as passenger (required if `passenger` is true)
|
- seatsRequested: number of seats requested as passenger (required if `passenger` is true)
|
||||||
- strict (boolean): if set to true, allow matching only with similar frequency ads
|
- strict (boolean): if set to true, allow matching only with similar frequency ads
|
||||||
- waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
- waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
||||||
|
- comment: optional freetext comment / description about the ad
|
||||||
|
|
||||||
## Messages
|
## Messages
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=public"
|
|||||||
RMQ_URI=amqp://v3-ad-broker:5672
|
RMQ_URI=amqp://v3-ad-broker:5672
|
||||||
|
|
||||||
# MESSAGE BROKER
|
# MESSAGE BROKER
|
||||||
BROKER_IMAGE=rabbitmq:3-alpine
|
BROKER_IMAGE=docker.io/rabbitmq:3-alpine
|
||||||
|
|
||||||
# POSTGRES
|
# POSTGRES
|
||||||
POSTGRES_IMAGE=postgres:15.0
|
POSTGRES_IMAGE=docker.io/postgres:15.0
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# BUILD FOR CI TESTING
|
# BUILD FOR CI TESTING
|
||||||
###################
|
###################
|
||||||
|
|
||||||
FROM node:18-alpine3.16
|
FROM docker.io/node:lts-hydrogen
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|||||||
2328
package-lock.json
generated
2328
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
80
package.json
80
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mobicoop/ad",
|
"name": "@mobicoop/ad",
|
||||||
"version": "2.2.5",
|
"version": "2.4.5",
|
||||||
"description": "Mobicoop V3 Ad",
|
"description": "Mobicoop V3 Ad",
|
||||||
"author": "sbriat",
|
"author": "sbriat",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -24,61 +24,61 @@
|
|||||||
"test:integration:ci": "npm run migrate:test:ci && dotenv -e ci/.env.ci -- jest --testPathPattern 'tests/integration/' --runInBand",
|
"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:cov": "jest --testPathPattern 'tests/unit/' --coverage",
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||||
"migrate": "docker exec v3-ad-api sh -c 'npx prisma migrate dev'",
|
"migrate": "docker exec v3-ad-api sh -c 'npx prisma migrate deploy'",
|
||||||
|
"migrate:dev": "docker exec v3-ad-api sh -c 'npx prisma migrate dev'",
|
||||||
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
|
"migrate:test": "dotenv -e .env.test -- npx prisma migrate deploy",
|
||||||
"migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
|
"migrate:test:ci": "dotenv -e ci/.env.ci -- npx prisma migrate deploy",
|
||||||
"migrate:deploy": "npx prisma migrate deploy"
|
"migrate:deploy": "npx prisma migrate deploy"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^1.9.9",
|
"@grpc/grpc-js": "^1.9.14",
|
||||||
"@grpc/proto-loader": "^0.7.10",
|
"@grpc/proto-loader": "^0.7.10",
|
||||||
"@songkeys/nestjs-redis": "^10.0.0",
|
"@songkeys/nestjs-redis": "^10.0.0",
|
||||||
"@mobicoop/ddd-library": "^2.1.1",
|
"@mobicoop/ddd-library": "^2.4.3",
|
||||||
"@mobicoop/health-module": "^2.3.1",
|
"@mobicoop/health-module": "^2.3.2",
|
||||||
"@mobicoop/message-broker-module": "^2.1.1",
|
"@mobicoop/message-broker-module": "^2.1.2",
|
||||||
"@nestjs/common": "^10.2.7",
|
"@nestjs/common": "^10.3.0",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.2.7",
|
"@nestjs/core": "^10.3.0",
|
||||||
"@nestjs/cqrs": "^10.2.6",
|
"@nestjs/cqrs": "^10.2.6",
|
||||||
"@nestjs/event-emitter": "^2.0.2",
|
"@nestjs/event-emitter": "^2.0.3",
|
||||||
"@nestjs/microservices": "^10.2.7",
|
"@nestjs/microservices": "^10.3.0",
|
||||||
"@nestjs/platform-express": "^10.2.7",
|
"@nestjs/platform-express": "^10.3.0",
|
||||||
"@nestjs/terminus": "^10.1.1",
|
"@nestjs/terminus": "^10.2.0",
|
||||||
"@prisma/client": "^5.5.2",
|
"@prisma/client": "^5.8.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.1",
|
||||||
"geo-tz": "^7.0.7",
|
"geo-tz": "^8.0.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"nestjs-request-context": "^3.0.0",
|
"nestjs-request-context": "^3.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"timezonecomplete": "^5.12.4"
|
"timezonecomplete": "^5.12.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.2.1",
|
"@nestjs/cli": "^10.3.0",
|
||||||
"@nestjs/schematics": "^10.0.3",
|
"@nestjs/schematics": "^10.1.0",
|
||||||
"@nestjs/testing": "^10.2.7",
|
"@nestjs/testing": "^10.3.0",
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^4.17.21",
|
||||||
"@types/jest": "29.5.7",
|
"@types/jest": "29.5.11",
|
||||||
"@types/node": "20.8.10",
|
"@types/node": "20.11.5",
|
||||||
"@types/supertest": "^2.0.15",
|
"@types/supertest": "^6.0.2",
|
||||||
"@types/uuid": "^9.0.6",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.9.1",
|
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||||
"@typescript-eslint/parser": "^6.9.1",
|
"@typescript-eslint/parser": "^6.19.0",
|
||||||
"dotenv-cli": "^7.3.0",
|
"dotenv-cli": "^7.3.0",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.2.3",
|
||||||
"prisma": "^5.5.2",
|
"prisma": "^5.8.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.4",
|
||||||
"ts-jest": "29.1.1",
|
"ts-jest": "29.1.1",
|
||||||
"ts-loader": "^9.5.0",
|
"ts-loader": "^9.5.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"tsconfig-paths": "4.2.0",
|
"tsconfig-paths": "4.2.0",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
@@ -96,13 +96,13 @@
|
|||||||
"prisma.service.ts",
|
"prisma.service.ts",
|
||||||
"main.ts"
|
"main.ts"
|
||||||
],
|
],
|
||||||
"rootDir": "src",
|
"rootDir": ".",
|
||||||
"testRegex": ".*\\.spec\\.ts$",
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
"transform": {
|
"transform": {
|
||||||
"^.+\\.(t|j)s$": "ts-jest"
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
},
|
},
|
||||||
"collectCoverageFrom": [
|
"collectCoverageFrom": [
|
||||||
"**/*.(t|j)s"
|
"./src/**/*.(t|j)s"
|
||||||
],
|
],
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
".constants.ts",
|
".constants.ts",
|
||||||
@@ -114,10 +114,10 @@
|
|||||||
"prisma.service.ts",
|
"prisma.service.ts",
|
||||||
"main.ts"
|
"main.ts"
|
||||||
],
|
],
|
||||||
"coverageDirectory": "../coverage",
|
"coverageDirectory": "coverage",
|
||||||
"moduleNameMapper": {
|
"moduleNameMapper": {
|
||||||
"^@modules(.*)": "<rootDir>/modules/$1",
|
"^@modules(.*)": "<rootDir>/src/modules/$1",
|
||||||
"^@src(.*)": "<rootDir>$1"
|
"^@src(.*)": "<rootDir>/src/$1"
|
||||||
},
|
},
|
||||||
"testEnvironment": "node"
|
"testEnvironment": "node"
|
||||||
}
|
}
|
||||||
|
|||||||
5
prisma/migrations/20231205142727_status/migration.sql
Normal file
5
prisma/migrations/20231205142727_status/migration.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Status" AS ENUM ('PENDING', 'VALID', 'INVALID', 'SUSPENDED');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ad" ADD COLUMN "status" "Status" NOT NULL DEFAULT 'PENDING';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ad" ADD COLUMN "comment" TEXT;
|
||||||
@@ -14,6 +14,7 @@ datasource db {
|
|||||||
model Ad {
|
model Ad {
|
||||||
uuid String @id @default(uuid()) @db.Uuid
|
uuid String @id @default(uuid()) @db.Uuid
|
||||||
userUuid String @db.Uuid
|
userUuid String @db.Uuid
|
||||||
|
status Status @default(PENDING)
|
||||||
driver Boolean
|
driver Boolean
|
||||||
passenger Boolean
|
passenger Boolean
|
||||||
frequency Frequency
|
frequency Frequency
|
||||||
@@ -26,6 +27,7 @@ model Ad {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @default(now()) @updatedAt
|
updatedAt DateTime @default(now()) @updatedAt
|
||||||
waypoints Waypoint[]
|
waypoints Waypoint[]
|
||||||
|
comment String?
|
||||||
|
|
||||||
@@map("ad")
|
@@map("ad")
|
||||||
}
|
}
|
||||||
@@ -66,3 +68,10 @@ enum Frequency {
|
|||||||
PUNCTUAL
|
PUNCTUAL
|
||||||
RECURRENT
|
RECURRENT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Status {
|
||||||
|
PENDING
|
||||||
|
VALID
|
||||||
|
INVALID
|
||||||
|
SUSPENDED
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,19 @@ export const SERVICE_NAME = 'ad';
|
|||||||
export const GRPC_PACKAGE_NAME = 'ad';
|
export const GRPC_PACKAGE_NAME = 'ad';
|
||||||
export const GRPC_SERVICE_NAME = 'AdService';
|
export const GRPC_SERVICE_NAME = 'AdService';
|
||||||
|
|
||||||
// messaging
|
// messaging output
|
||||||
export const AD_CREATED_ROUTING_KEY = 'ad.created';
|
export const AD_CREATED_ROUTING_KEY = 'ad.created';
|
||||||
|
|
||||||
|
// messaging input
|
||||||
|
export const MATCHER_AD_CREATED_MESSAGE_HANDLER = 'matcherAdCreated';
|
||||||
|
export const MATCHER_AD_CREATED_ROUTING_KEY = 'matcher-ad.created';
|
||||||
|
export const MATCHER_AD_CREATED_QUEUE = 'ad.matcher-ad.created';
|
||||||
|
export const MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER =
|
||||||
|
'matcherAdCreationFailed';
|
||||||
|
export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
|
||||||
|
'matcher-ad.creation-failed';
|
||||||
|
export const MATCHER_AD_CREATION_FAILED_QUEUE = 'ad.matcher-ad.creation-failed';
|
||||||
|
|
||||||
// configuration
|
// configuration
|
||||||
export const SERVICE_CONFIGURATION_SET_QUEUE = 'ad-configuration-set';
|
export const SERVICE_CONFIGURATION_SET_QUEUE = 'ad-configuration-set';
|
||||||
export const SERVICE_CONFIGURATION_DELETE_QUEUE = 'ad-configuration-delete';
|
export const SERVICE_CONFIGURATION_DELETE_QUEUE = 'ad-configuration-delete';
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
WaypointModel,
|
WaypointModel,
|
||||||
ScheduleItemModel,
|
ScheduleItemModel,
|
||||||
} from './infrastructure/ad.repository';
|
} from './infrastructure/ad.repository';
|
||||||
import { Frequency } from './core/domain/ad.types';
|
import { Frequency, Status } from './core/domain/ad.types';
|
||||||
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
|
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
|
||||||
@@ -39,46 +39,50 @@ export class AdMapper
|
|||||||
userUuid: copy.userId,
|
userUuid: copy.userId,
|
||||||
driver: copy.driver as boolean,
|
driver: copy.driver as boolean,
|
||||||
passenger: copy.passenger as boolean,
|
passenger: copy.passenger as boolean,
|
||||||
|
status: copy.status,
|
||||||
frequency: copy.frequency,
|
frequency: copy.frequency,
|
||||||
fromDate: new Date(copy.fromDate),
|
fromDate: new Date(copy.fromDate),
|
||||||
toDate: new Date(copy.toDate),
|
toDate: new Date(copy.toDate),
|
||||||
schedule: {
|
schedule: copy.schedule
|
||||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
? {
|
||||||
uuid: v4(),
|
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||||
day: scheduleItem.day as number,
|
uuid: v4(),
|
||||||
time: new Date(
|
day: scheduleItem.day as number,
|
||||||
1970,
|
time: new Date(
|
||||||
0,
|
1970,
|
||||||
1,
|
0,
|
||||||
parseInt(scheduleItem.time.split(':')[0]),
|
1,
|
||||||
parseInt(scheduleItem.time.split(':')[1]),
|
parseInt(scheduleItem.time.split(':')[0]),
|
||||||
),
|
parseInt(scheduleItem.time.split(':')[1]),
|
||||||
margin: scheduleItem.margin as number,
|
),
|
||||||
createdAt: now,
|
margin: scheduleItem.margin as number,
|
||||||
updatedAt: now,
|
createdAt: now,
|
||||||
})),
|
updatedAt: now,
|
||||||
},
|
})),
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
seatsProposed: copy.seatsProposed as number,
|
seatsProposed: copy.seatsProposed as number,
|
||||||
seatsRequested: copy.seatsRequested as number,
|
seatsRequested: copy.seatsRequested as number,
|
||||||
strict: copy.strict as boolean,
|
strict: copy.strict as boolean,
|
||||||
waypoints: {
|
waypoints: copy.waypoints
|
||||||
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
? {
|
||||||
uuid: v4(),
|
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
||||||
position: waypoint.position,
|
uuid: v4(),
|
||||||
name: waypoint.address.name,
|
position: waypoint.position,
|
||||||
houseNumber: waypoint.address.houseNumber,
|
name: waypoint.address.name,
|
||||||
street: waypoint.address.street,
|
houseNumber: waypoint.address.houseNumber,
|
||||||
locality: waypoint.address.locality,
|
street: waypoint.address.street,
|
||||||
postalCode: waypoint.address.postalCode,
|
locality: waypoint.address.locality,
|
||||||
country: waypoint.address.country,
|
postalCode: waypoint.address.postalCode,
|
||||||
lon: waypoint.address.coordinates.lon,
|
country: waypoint.address.country,
|
||||||
lat: waypoint.address.coordinates.lat,
|
lon: waypoint.address.coordinates.lon,
|
||||||
createdAt: now,
|
lat: waypoint.address.coordinates.lat,
|
||||||
updatedAt: now,
|
createdAt: now,
|
||||||
})),
|
updatedAt: now,
|
||||||
},
|
})),
|
||||||
createdAt: copy.createdAt,
|
}
|
||||||
updatedAt: copy.updatedAt,
|
: undefined,
|
||||||
|
comment: copy.comment,
|
||||||
};
|
};
|
||||||
return record;
|
return record;
|
||||||
};
|
};
|
||||||
@@ -92,10 +96,11 @@ export class AdMapper
|
|||||||
userId: record.userUuid,
|
userId: record.userUuid,
|
||||||
driver: record.driver,
|
driver: record.driver,
|
||||||
passenger: record.passenger,
|
passenger: record.passenger,
|
||||||
|
status: record.status as Status,
|
||||||
frequency: record.frequency as Frequency,
|
frequency: record.frequency as Frequency,
|
||||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||||
toDate: record.toDate.toISOString().split('T')[0],
|
toDate: record.toDate.toISOString().split('T')[0],
|
||||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
schedule: record.schedule?.map((scheduleItem: ScheduleItemModel) => ({
|
||||||
day: scheduleItem.day,
|
day: scheduleItem.day,
|
||||||
time: `${scheduleItem.time
|
time: `${scheduleItem.time
|
||||||
.getUTCHours()
|
.getUTCHours()
|
||||||
@@ -109,7 +114,7 @@ export class AdMapper
|
|||||||
seatsProposed: record.seatsProposed,
|
seatsProposed: record.seatsProposed,
|
||||||
seatsRequested: record.seatsRequested,
|
seatsRequested: record.seatsRequested,
|
||||||
strict: record.strict,
|
strict: record.strict,
|
||||||
waypoints: record.waypoints.map((waypoint: WaypointModel) => ({
|
waypoints: record.waypoints?.map((waypoint: WaypointModel) => ({
|
||||||
position: waypoint.position,
|
position: waypoint.position,
|
||||||
address: {
|
address: {
|
||||||
name: waypoint.name,
|
name: waypoint.name,
|
||||||
@@ -124,6 +129,7 @@ export class AdMapper
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
comment: record.comment,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return entity;
|
return entity;
|
||||||
@@ -135,6 +141,7 @@ export class AdMapper
|
|||||||
response.userId = props.userId;
|
response.userId = props.userId;
|
||||||
response.driver = props.driver as boolean;
|
response.driver = props.driver as boolean;
|
||||||
response.passenger = props.passenger as boolean;
|
response.passenger = props.passenger as boolean;
|
||||||
|
response.status = props.status;
|
||||||
response.frequency = props.frequency;
|
response.frequency = props.frequency;
|
||||||
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
||||||
{
|
{
|
||||||
@@ -188,6 +195,7 @@ export class AdMapper
|
|||||||
lon: waypoint.address.coordinates.lon,
|
lon: waypoint.address.coordinates.lon,
|
||||||
lat: waypoint.address.coordinates.lat,
|
lat: waypoint.address.coordinates.lat,
|
||||||
}));
|
}));
|
||||||
|
response.comment = props.comment;
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,16 +21,42 @@ import { PrismaService } from './infrastructure/prisma.service';
|
|||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
||||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||||
|
import { FindAdsByIdsGrpcController } from './interface/grpc-controllers/find-ads-by-ids.grpc.controller';
|
||||||
|
import { FindAdsByIdsQueryHandler } from './core/application/queries/find-ads-by-ids/find-ads-by-ids.query-handler';
|
||||||
|
import { MatcherAdCreatedMessageHandler } from './interface/message-handlers/matcher-ad-created.message-handler';
|
||||||
|
import { ValidateAdService } from './core/application/commands/validate-ad/validate-ad.service';
|
||||||
|
import { MatcherAdCreationFailedMessageHandler } from './interface/message-handlers/matcher-ad-creation-failed.message-handler';
|
||||||
|
import { InvalidateAdService } from './core/application/commands/invalidate-ad/invalidate-ad.service';
|
||||||
|
import { FindAdsByUserIdGrpcController } from './interface/grpc-controllers/find-ads-by-user-id.grpc.controller';
|
||||||
|
import { FindAdsByUserIdQueryHandler } from './core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query-handler';
|
||||||
|
|
||||||
const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController];
|
const grpcControllers = [
|
||||||
|
CreateAdGrpcController,
|
||||||
|
FindAdByIdGrpcController,
|
||||||
|
FindAdsByIdsGrpcController,
|
||||||
|
FindAdsByUserIdGrpcController,
|
||||||
|
];
|
||||||
|
|
||||||
|
const messageHandlers = [
|
||||||
|
MatcherAdCreatedMessageHandler,
|
||||||
|
MatcherAdCreationFailedMessageHandler,
|
||||||
|
];
|
||||||
|
|
||||||
const eventHandlers: Provider[] = [
|
const eventHandlers: Provider[] = [
|
||||||
PublishMessageWhenAdIsCreatedDomainEventHandler,
|
PublishMessageWhenAdIsCreatedDomainEventHandler,
|
||||||
];
|
];
|
||||||
|
|
||||||
const commandHandlers: Provider[] = [CreateAdService];
|
const commandHandlers: Provider[] = [
|
||||||
|
CreateAdService,
|
||||||
|
ValidateAdService,
|
||||||
|
InvalidateAdService,
|
||||||
|
];
|
||||||
|
|
||||||
const queryHandlers: Provider[] = [FindAdByIdQueryHandler];
|
const queryHandlers: Provider[] = [
|
||||||
|
FindAdByIdQueryHandler,
|
||||||
|
FindAdsByIdsQueryHandler,
|
||||||
|
FindAdsByUserIdQueryHandler,
|
||||||
|
];
|
||||||
|
|
||||||
const mappers: Provider[] = [AdMapper];
|
const mappers: Provider[] = [AdMapper];
|
||||||
|
|
||||||
@@ -72,6 +98,7 @@ const adapters: Provider[] = [
|
|||||||
imports: [CqrsModule],
|
imports: [CqrsModule],
|
||||||
controllers: [...grpcControllers],
|
controllers: [...grpcControllers],
|
||||||
providers: [
|
providers: [
|
||||||
|
...messageHandlers,
|
||||||
...eventHandlers,
|
...eventHandlers,
|
||||||
...commandHandlers,
|
...commandHandlers,
|
||||||
...queryHandlers,
|
...queryHandlers,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export class CreateAdCommand extends Command {
|
|||||||
readonly seatsRequested?: number;
|
readonly seatsRequested?: number;
|
||||||
readonly strict: boolean;
|
readonly strict: boolean;
|
||||||
readonly waypoints: Waypoint[];
|
readonly waypoints: Waypoint[];
|
||||||
|
readonly comment?: string;
|
||||||
|
|
||||||
constructor(props: CommandProps<CreateAdCommand>) {
|
constructor(props: CommandProps<CreateAdCommand>) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -29,5 +30,6 @@ export class CreateAdCommand extends Command {
|
|||||||
this.seatsRequested = props.seatsRequested;
|
this.seatsRequested = props.seatsRequested;
|
||||||
this.strict = props.strict;
|
this.strict = props.strict;
|
||||||
this.waypoints = props.waypoints;
|
this.waypoints = props.waypoints;
|
||||||
|
this.comment = props.comment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export class CreateAdService implements ICommandHandler {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
comment: command.comment,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class InvalidateAdCommand extends Command {
|
||||||
|
constructor(props: CommandProps<InvalidateAdCommand>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { InvalidateAdCommand } from './invalidate-ad.command';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { AggregateID } from '@mobicoop/ddd-library';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
|
||||||
|
@CommandHandler(InvalidateAdCommand)
|
||||||
|
export class InvalidateAdService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: InvalidateAdCommand): Promise<AggregateID> {
|
||||||
|
const ad: AdEntity = await this.repository.findOneById(command.id, {
|
||||||
|
waypoints: true,
|
||||||
|
schedule: true,
|
||||||
|
});
|
||||||
|
ad.invalid();
|
||||||
|
await this.repository.update(ad.id, ad);
|
||||||
|
return ad.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class ValidateAdCommand extends Command {
|
||||||
|
constructor(props: CommandProps<ValidateAdCommand>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
|
import { ValidateAdCommand } from './validate-ad.command';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { AggregateID } from '@mobicoop/ddd-library';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
|
||||||
|
@CommandHandler(ValidateAdCommand)
|
||||||
|
export class ValidateAdService implements ICommandHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(command: ValidateAdCommand): Promise<AggregateID> {
|
||||||
|
const ad: AdEntity = await this.repository.findOneById(command.id, {
|
||||||
|
waypoints: false,
|
||||||
|
schedule: false,
|
||||||
|
});
|
||||||
|
ad.valid();
|
||||||
|
await this.repository.update(ad.id, ad);
|
||||||
|
return ad.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
import { AdCreatedDomainEvent } from '../../domain/events/ad-created.domain-events';
|
import { AdCreatedDomainEvent } from '../../domain/events/ad-created.domain-event';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
|
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
|
||||||
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';
|
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { AdEntity } from '../../../domain/ad.entity';
|
||||||
|
import { FindAdsByIdsQuery } from './find-ads-by-ids.query';
|
||||||
|
|
||||||
|
@QueryHandler(FindAdsByIdsQuery)
|
||||||
|
export class FindAdsByIdsQueryHandler implements IQueryHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
) {}
|
||||||
|
async execute(query: FindAdsByIdsQuery): Promise<AdEntity[]> {
|
||||||
|
return await this.repository.findAllByIds(query.ids, {
|
||||||
|
waypoints: true,
|
||||||
|
schedule: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { QueryBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class FindAdsByIdsQuery extends QueryBase {
|
||||||
|
readonly ids: string[];
|
||||||
|
|
||||||
|
constructor(ids: string[]) {
|
||||||
|
super();
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { AdEntity } from '../../../domain/ad.entity';
|
||||||
|
import { FindAdsByUserIdQuery } from './find-ads-by-user-id.query';
|
||||||
|
|
||||||
|
@QueryHandler(FindAdsByUserIdQuery)
|
||||||
|
export class FindAdsByUserIdQueryHandler implements IQueryHandler {
|
||||||
|
constructor(
|
||||||
|
@Inject(AD_REPOSITORY)
|
||||||
|
private readonly repository: AdRepositoryPort,
|
||||||
|
) {}
|
||||||
|
async execute(query: FindAdsByUserIdQuery): Promise<AdEntity[]> {
|
||||||
|
return await this.repository.findAll(
|
||||||
|
{
|
||||||
|
userUuid: query.userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
waypoints: true,
|
||||||
|
schedule: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { QueryBase } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class FindAdsByUserIdQuery extends QueryBase {
|
||||||
|
readonly userId: string;
|
||||||
|
|
||||||
|
constructor(userId: string) {
|
||||||
|
super();
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
import { AdCreatedDomainEvent } from './events/ad-created.domain-event';
|
||||||
import { AdProps, CreateAdProps } from './ad.types';
|
import { AdProps, CreateAdProps, Status } from './ad.types';
|
||||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||||
|
import { AdValidatedDomainEvent } from './events/ad-validated.domain-event';
|
||||||
|
import { AdInvalidatedDomainEvent } from './events/ad-invalidated.domain-event';
|
||||||
|
import { AdSuspendedDomainEvent } from './events/ad-suspended.domain-event';
|
||||||
|
|
||||||
export class AdEntity extends AggregateRoot<AdProps> {
|
export class AdEntity extends AggregateRoot<AdProps> {
|
||||||
protected readonly _id: AggregateID;
|
protected readonly _id: AggregateID;
|
||||||
|
|
||||||
static create = (create: CreateAdProps): AdEntity => {
|
static create = (create: CreateAdProps): AdEntity => {
|
||||||
const id = v4();
|
const id = v4();
|
||||||
const props: AdProps = { ...create };
|
const props: AdProps = { ...create, status: Status.PENDING };
|
||||||
const ad = new AdEntity({ id, props });
|
const ad = new AdEntity({ id, props });
|
||||||
ad.addEvent(
|
ad.addEvent(
|
||||||
new AdCreatedDomainEvent({
|
new AdCreatedDomainEvent({
|
||||||
|
metadata: {
|
||||||
|
correlationId: id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
aggregateId: id,
|
aggregateId: id,
|
||||||
userId: props.userId,
|
userId: props.userId,
|
||||||
driver: props.driver,
|
driver: props.driver,
|
||||||
@@ -40,11 +47,54 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
|||||||
lon: waypoint.address.coordinates.lon,
|
lon: waypoint.address.coordinates.lon,
|
||||||
lat: waypoint.address.coordinates.lat,
|
lat: waypoint.address.coordinates.lat,
|
||||||
})),
|
})),
|
||||||
|
comment: props.comment,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return ad;
|
return ad;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
valid = (): AdEntity => {
|
||||||
|
this.props.status = Status.VALID;
|
||||||
|
this.addEvent(
|
||||||
|
new AdValidatedDomainEvent({
|
||||||
|
metadata: {
|
||||||
|
correlationId: this.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
aggregateId: this.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
invalid = (): AdEntity => {
|
||||||
|
this.props.status = Status.INVALID;
|
||||||
|
this.addEvent(
|
||||||
|
new AdInvalidatedDomainEvent({
|
||||||
|
metadata: {
|
||||||
|
correlationId: this.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
aggregateId: this.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
suspend = (): AdEntity => {
|
||||||
|
this.props.status = Status.SUSPENDED;
|
||||||
|
this.addEvent(
|
||||||
|
new AdSuspendedDomainEvent({
|
||||||
|
metadata: {
|
||||||
|
correlationId: this.id,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
},
|
||||||
|
aggregateId: this.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { WaypointProps } from './value-objects/waypoint.value-object';
|
|||||||
export interface AdProps {
|
export interface AdProps {
|
||||||
userId: string;
|
userId: string;
|
||||||
driver: boolean;
|
driver: boolean;
|
||||||
|
status: Status;
|
||||||
passenger: boolean;
|
passenger: boolean;
|
||||||
frequency: Frequency;
|
frequency: Frequency;
|
||||||
fromDate: string;
|
fromDate: string;
|
||||||
@@ -14,6 +15,7 @@ export interface AdProps {
|
|||||||
seatsRequested: number;
|
seatsRequested: number;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
waypoints: WaypointProps[];
|
waypoints: WaypointProps[];
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Properties that are needed for an Ad creation
|
// Properties that are needed for an Ad creation
|
||||||
@@ -29,9 +31,17 @@ export interface CreateAdProps {
|
|||||||
seatsRequested: number;
|
seatsRequested: number;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
waypoints: WaypointProps[];
|
waypoints: WaypointProps[];
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Frequency {
|
export enum Frequency {
|
||||||
PUNCTUAL = 'PUNCTUAL',
|
PUNCTUAL = 'PUNCTUAL',
|
||||||
RECURRENT = 'RECURRENT',
|
RECURRENT = 'RECURRENT',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Status {
|
||||||
|
PENDING = 'PENDING',
|
||||||
|
VALID = 'VALID',
|
||||||
|
INVALID = 'INVALID',
|
||||||
|
SUSPENDED = 'SUSPENDED',
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||||||
readonly seatsRequested: number;
|
readonly seatsRequested: number;
|
||||||
readonly strict: boolean;
|
readonly strict: boolean;
|
||||||
readonly waypoints: Waypoint[];
|
readonly waypoints: Waypoint[];
|
||||||
|
readonly comment?: string;
|
||||||
|
|
||||||
constructor(props: DomainEventProps<AdCreatedDomainEvent>) {
|
constructor(props: DomainEventProps<AdCreatedDomainEvent>) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -26,6 +27,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
|
|||||||
this.seatsRequested = props.seatsRequested;
|
this.seatsRequested = props.seatsRequested;
|
||||||
this.strict = props.strict;
|
this.strict = props.strict;
|
||||||
this.waypoints = props.waypoints;
|
this.waypoints = props.waypoints;
|
||||||
|
this.comment = props.comment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AdInvalidatedDomainEvent extends DomainEvent {
|
||||||
|
constructor(props: DomainEventProps<AdInvalidatedDomainEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AdSuspendedDomainEvent extends DomainEvent {
|
||||||
|
constructor(props: DomainEventProps<AdSuspendedDomainEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export class AdValidatedDomainEvent extends DomainEvent {
|
||||||
|
constructor(props: DomainEventProps<AdValidatedDomainEvent>) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,28 +17,34 @@ export type AdBaseModel = {
|
|||||||
userUuid: string;
|
userUuid: string;
|
||||||
driver: boolean;
|
driver: boolean;
|
||||||
passenger: boolean;
|
passenger: boolean;
|
||||||
|
status: string;
|
||||||
frequency: string;
|
frequency: string;
|
||||||
fromDate: Date;
|
fromDate: Date;
|
||||||
toDate: Date;
|
toDate: Date;
|
||||||
seatsProposed: number;
|
seatsProposed: number;
|
||||||
seatsRequested: number;
|
seatsRequested: number;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
createdAt: Date;
|
comment?: string;
|
||||||
updatedAt: Date;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AdReadModel = AdBaseModel & {
|
export type AdReadModel = AdBaseModel & {
|
||||||
waypoints: WaypointModel[];
|
waypoints: WaypointModel[];
|
||||||
schedule: ScheduleItemModel[];
|
schedule: ScheduleItemModel[];
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AdWriteModel = AdBaseModel & {
|
export type AdWriteModel = AdBaseModel & {
|
||||||
waypoints: {
|
schedule: ScheduleWriteModel | undefined;
|
||||||
create: WaypointModel[];
|
waypoints: WaypointWriteModel | undefined;
|
||||||
};
|
};
|
||||||
schedule: {
|
|
||||||
create: ScheduleItemModel[];
|
export type ScheduleWriteModel = {
|
||||||
};
|
create: ScheduleItemModel[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WaypointWriteModel = {
|
||||||
|
create: WaypointModel[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ScheduleItemModel = {
|
export type ScheduleItemModel = {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { ResponseBase } from '@mobicoop/ddd-library';
|
import { ResponseBase } from '@mobicoop/ddd-library';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency, Status } from '@modules/ad/core/domain/ad.types';
|
||||||
|
|
||||||
export class AdResponseDto extends ResponseBase {
|
export class AdResponseDto extends ResponseBase {
|
||||||
userId: string;
|
userId: string;
|
||||||
driver: boolean;
|
driver: boolean;
|
||||||
passenger: boolean;
|
passenger: boolean;
|
||||||
|
status: Status;
|
||||||
frequency: Frequency;
|
frequency: Frequency;
|
||||||
fromDate: string;
|
fromDate: string;
|
||||||
toDate: string;
|
toDate: string;
|
||||||
@@ -27,4 +28,5 @@ export class AdResponseDto extends ResponseBase {
|
|||||||
lon: number;
|
lon: number;
|
||||||
lat: number;
|
lat: number;
|
||||||
}[];
|
}[];
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/modules/ad/interface/dtos/ads.response.dto.ts
Normal file
5
src/modules/ad/interface/dtos/ads.response.dto.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { AdResponseDto } from './ad.response.dto';
|
||||||
|
|
||||||
|
export class AdsResponseDto {
|
||||||
|
readonly ads: readonly AdResponseDto[];
|
||||||
|
}
|
||||||
@@ -4,7 +4,8 @@ package ad;
|
|||||||
|
|
||||||
service AdService {
|
service AdService {
|
||||||
rpc FindOneById(AdById) returns (Ad);
|
rpc FindOneById(AdById) returns (Ad);
|
||||||
rpc FindAll(AdFilter) returns (Ads);
|
rpc FindAllByIds(AdsById) returns (Ads);
|
||||||
|
rpc FindAllByUserId(UserById) returns (Ads);
|
||||||
rpc Create(Ad) returns (AdById);
|
rpc Create(Ad) returns (AdById);
|
||||||
rpc Update(Ad) returns (Ad);
|
rpc Update(Ad) returns (Ad);
|
||||||
rpc Delete(AdById) returns (Empty);
|
rpc Delete(AdById) returns (Empty);
|
||||||
@@ -14,6 +15,14 @@ message AdById {
|
|||||||
string id = 1;
|
string id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UserById {
|
||||||
|
string id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message AdsById {
|
||||||
|
repeated string ids = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Ad {
|
message Ad {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string userId = 2;
|
string userId = 2;
|
||||||
@@ -27,6 +36,7 @@ message Ad {
|
|||||||
int32 seatsRequested = 10;
|
int32 seatsRequested = 10;
|
||||||
bool strict = 11;
|
bool strict = 11;
|
||||||
repeated Waypoint waypoints = 12;
|
repeated Waypoint waypoints = 12;
|
||||||
|
optional string comment = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ScheduleItem {
|
message ScheduleItem {
|
||||||
@@ -52,14 +62,8 @@ enum Frequency {
|
|||||||
RECURRENT = 2;
|
RECURRENT = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AdFilter {
|
|
||||||
int32 page = 1;
|
|
||||||
int32 perPage = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Ads {
|
message Ads {
|
||||||
repeated Ad data = 1;
|
repeated Ad ads = 1;
|
||||||
int32 total = 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Empty {}
|
message Empty {}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import {
|
|||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsInt,
|
IsInt,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
|
IsString,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
ArrayMinSize,
|
ArrayMinSize,
|
||||||
|
Length,
|
||||||
IsUUID,
|
IsUUID,
|
||||||
IsArray,
|
IsArray,
|
||||||
IsISO8601,
|
IsISO8601,
|
||||||
@@ -78,4 +80,9 @@ export class CreateAdRequestDto {
|
|||||||
@HasValidPositionIndexes()
|
@HasValidPositionIndexes()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
waypoints: WaypointDto[];
|
waypoints: WaypointDto[];
|
||||||
|
|
||||||
|
@Length(0, 2000)
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
comment?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { ArrayMinSize, IsArray } from 'class-validator';
|
||||||
|
|
||||||
|
export class FindAdsByIdsRequestDto {
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
|
ids: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class FindAdsByUserIdRequestDto {
|
||||||
|
@IsString()
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
|
import { GRPC_SERVICE_NAME } from '@src/app.constants';
|
||||||
|
import { FindAdsByIdsRequestDto } from './dtos/find-ads-by-ids.request.dto';
|
||||||
|
import { AdsResponseDto } from '../dtos/ads.response.dto';
|
||||||
|
import { FindAdsByIdsQuery } from '@modules/ad/core/application/queries/find-ads-by-ids/find-ads-by-ids.query';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: false,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
@Controller()
|
||||||
|
export class FindAdsByIdsGrpcController {
|
||||||
|
constructor(
|
||||||
|
protected readonly mapper: AdMapper,
|
||||||
|
private readonly queryBus: QueryBus,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@GrpcMethod(GRPC_SERVICE_NAME, 'FindAllByIds')
|
||||||
|
async findAllByIds(data: FindAdsByIdsRequestDto): Promise<AdsResponseDto> {
|
||||||
|
try {
|
||||||
|
const ads: AdEntity[] = await this.queryBus.execute(
|
||||||
|
new FindAdsByIdsQuery(data.ids),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
ads: ads.map((ad: AdEntity) => this.mapper.toResponse(ad)),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
|
import { GRPC_SERVICE_NAME } from '@src/app.constants';
|
||||||
|
import { AdsResponseDto } from '../dtos/ads.response.dto';
|
||||||
|
import { FindAdsByUserIdQuery } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query';
|
||||||
|
import { FindAdsByUserIdRequestDto } from './dtos/find-ads-by-user-id.request.dto';
|
||||||
|
|
||||||
|
@UsePipes(
|
||||||
|
new RpcValidationPipe({
|
||||||
|
whitelist: false,
|
||||||
|
forbidUnknownValues: false,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
@Controller()
|
||||||
|
export class FindAdsByUserIdGrpcController {
|
||||||
|
constructor(
|
||||||
|
protected readonly mapper: AdMapper,
|
||||||
|
private readonly queryBus: QueryBus,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@GrpcMethod(GRPC_SERVICE_NAME, 'FindAllByUserId')
|
||||||
|
async findAllByUserId(
|
||||||
|
data: FindAdsByUserIdRequestDto,
|
||||||
|
): Promise<AdsResponseDto> {
|
||||||
|
try {
|
||||||
|
const ads: AdEntity[] = await this.queryBus.execute(
|
||||||
|
new FindAdsByUserIdQuery(data.id),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
ads: ads.map((ad: AdEntity) => this.mapper.toResponse(ad)),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
throw new RpcException({
|
||||||
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { MATCHER_AD_CREATED_MESSAGE_HANDLER } from '@src/app.constants';
|
||||||
|
import { MatcherAdCreatedIntegrationEvent } from './matcher-ad.types';
|
||||||
|
import { ValidateAdCommand } from '@modules/ad/core/application/commands/validate-ad/validate-ad.command';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MatcherAdCreatedMessageHandler {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@RabbitSubscribe({
|
||||||
|
name: MATCHER_AD_CREATED_MESSAGE_HANDLER,
|
||||||
|
})
|
||||||
|
public async matcherAdCreated(message: string) {
|
||||||
|
try {
|
||||||
|
const matcherAdCreatedIntegrationEvent: MatcherAdCreatedIntegrationEvent =
|
||||||
|
JSON.parse(message);
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new ValidateAdCommand({
|
||||||
|
id: matcherAdCreatedIntegrationEvent.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
// do not throw error to acknowledge incoming message
|
||||||
|
// error handling should be done in the command handler, if relevant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER } from '@src/app.constants';
|
||||||
|
import { MatcherAdCreationFailedIntegrationEvent } from './matcher-ad.types';
|
||||||
|
import { InvalidateAdCommand } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.command';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MatcherAdCreationFailedMessageHandler {
|
||||||
|
constructor(private readonly commandBus: CommandBus) {}
|
||||||
|
|
||||||
|
@RabbitSubscribe({
|
||||||
|
name: MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER,
|
||||||
|
})
|
||||||
|
public async matcherAdCreationFailed(message: string) {
|
||||||
|
try {
|
||||||
|
const matcherAdCreationFailedIntegrationEvent: MatcherAdCreationFailedIntegrationEvent =
|
||||||
|
JSON.parse(message);
|
||||||
|
await this.commandBus.execute(
|
||||||
|
new InvalidateAdCommand({
|
||||||
|
id: matcherAdCreationFailedIntegrationEvent.id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error: any) {
|
||||||
|
// do not throw error to acknowledge incoming message
|
||||||
|
// error handling should be done in the command handler, if relevant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { IntegrationEvent } from '@mobicoop/ddd-library';
|
||||||
|
|
||||||
|
export type MatcherAdCreatedIntegrationEvent = IntegrationEvent;
|
||||||
|
export type MatcherAdCreationFailedIntegrationEvent = IntegrationEvent;
|
||||||
@@ -2,7 +2,15 @@ import { Module, Provider } from '@nestjs/common';
|
|||||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||||
|
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { SERVICE_NAME } from '@src/app.constants';
|
import {
|
||||||
|
MATCHER_AD_CREATED_MESSAGE_HANDLER,
|
||||||
|
MATCHER_AD_CREATED_QUEUE,
|
||||||
|
MATCHER_AD_CREATED_ROUTING_KEY,
|
||||||
|
MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER,
|
||||||
|
MATCHER_AD_CREATION_FAILED_QUEUE,
|
||||||
|
MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
|
||||||
|
SERVICE_NAME,
|
||||||
|
} from '@src/app.constants';
|
||||||
import {
|
import {
|
||||||
MessageBrokerModule,
|
MessageBrokerModule,
|
||||||
MessageBrokerModuleOptions,
|
MessageBrokerModuleOptions,
|
||||||
@@ -24,6 +32,16 @@ const imports = [
|
|||||||
) as boolean,
|
) as boolean,
|
||||||
},
|
},
|
||||||
name: SERVICE_NAME,
|
name: SERVICE_NAME,
|
||||||
|
handlers: {
|
||||||
|
[MATCHER_AD_CREATED_MESSAGE_HANDLER]: {
|
||||||
|
routingKey: MATCHER_AD_CREATED_ROUTING_KEY,
|
||||||
|
queue: MATCHER_AD_CREATED_QUEUE,
|
||||||
|
},
|
||||||
|
[MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER]: {
|
||||||
|
routingKey: MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
|
||||||
|
queue: MATCHER_AD_CREATION_FAILED_QUEUE,
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
|
|||||||
import { AdMapper } from '@modules/ad/ad.mapper';
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency, Status } from '@modules/ad/core/domain/ad.types';
|
||||||
import {
|
import {
|
||||||
AdReadModel,
|
AdReadModel,
|
||||||
AdWriteModel,
|
AdWriteModel,
|
||||||
@@ -17,6 +17,7 @@ const adEntity: AdEntity = new AdEntity({
|
|||||||
userId: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
userId: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
||||||
driver: false,
|
driver: false,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
|
status: Status.PENDING,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
fromDate: '2023-06-21',
|
fromDate: '2023-06-21',
|
||||||
toDate: '2023-06-21',
|
toDate: '2023-06-21',
|
||||||
@@ -67,6 +68,7 @@ const adReadModel: AdReadModel = {
|
|||||||
userUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
userUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
|
||||||
driver: false,
|
driver: false,
|
||||||
passenger: true,
|
passenger: true,
|
||||||
|
status: Status.PENDING,
|
||||||
frequency: Frequency.PUNCTUAL,
|
frequency: Frequency.PUNCTUAL,
|
||||||
fromDate: new Date('2023-06-21'),
|
fromDate: new Date('2023-06-21'),
|
||||||
toDate: new Date('2023-06-21'),
|
toDate: new Date('2023-06-21'),
|
||||||
@@ -109,6 +111,7 @@ const adReadModel: AdReadModel = {
|
|||||||
strict: false,
|
strict: false,
|
||||||
seatsProposed: 3,
|
seatsProposed: 3,
|
||||||
seatsRequested: 1,
|
seatsRequested: 1,
|
||||||
|
comment: '',
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
};
|
};
|
||||||
@@ -142,9 +145,9 @@ describe('Ad Mapper', () => {
|
|||||||
|
|
||||||
it('should map domain entity to persistence data', async () => {
|
it('should map domain entity to persistence data', async () => {
|
||||||
const mapped: AdWriteModel = adMapper.toPersistence(adEntity);
|
const mapped: AdWriteModel = adMapper.toPersistence(adEntity);
|
||||||
expect(mapped.waypoints.create[0].uuid.length).toBe(36);
|
expect(mapped.waypoints?.create[0].uuid.length).toBe(36);
|
||||||
expect(mapped.waypoints.create[1].uuid.length).toBe(36);
|
expect(mapped.waypoints?.create[1].uuid.length).toBe(36);
|
||||||
expect(mapped.schedule.create.length).toBe(1);
|
expect(mapped.schedule?.create.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map persisted data to domain entity', async () => {
|
it('should map persisted data to domain entity', async () => {
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
import {
|
||||||
|
CreateAdProps,
|
||||||
|
Frequency,
|
||||||
|
Status,
|
||||||
|
} from '@modules/ad/core/domain/ad.types';
|
||||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
const originWaypointProps: WaypointProps = {
|
const originWaypointProps: WaypointProps = {
|
||||||
@@ -35,6 +39,7 @@ const baseCreateAdProps = {
|
|||||||
seatsRequested: 1,
|
seatsRequested: 1,
|
||||||
strict: false,
|
strict: false,
|
||||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||||
|
comment: "J'accepte les chiens mais pas les chats",
|
||||||
};
|
};
|
||||||
const punctualCreateAdProps = {
|
const punctualCreateAdProps = {
|
||||||
fromDate: '2023-06-21',
|
fromDate: '2023-06-21',
|
||||||
@@ -124,11 +129,15 @@ describe('Ad entity create', () => {
|
|||||||
punctualPassengerCreateAdProps,
|
punctualPassengerCreateAdProps,
|
||||||
);
|
);
|
||||||
expect(punctualPassengerAd.id.length).toBe(36);
|
expect(punctualPassengerAd.id.length).toBe(36);
|
||||||
|
expect(punctualPassengerAd.getProps().status).toBe(Status.PENDING);
|
||||||
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
||||||
expect(punctualPassengerAd.getProps().schedule[0].day).toBe(3);
|
expect(punctualPassengerAd.getProps().schedule[0].day).toBe(3);
|
||||||
expect(punctualPassengerAd.getProps().schedule[0].time).toBe('08:30');
|
expect(punctualPassengerAd.getProps().schedule[0].time).toBe('08:30');
|
||||||
expect(punctualPassengerAd.getProps().driver).toBeFalsy();
|
expect(punctualPassengerAd.getProps().driver).toBeFalsy();
|
||||||
expect(punctualPassengerAd.getProps().passenger).toBeTruthy();
|
expect(punctualPassengerAd.getProps().passenger).toBeTruthy();
|
||||||
|
expect(punctualPassengerAd.getProps().comment).toBe(
|
||||||
|
"J'accepte les chiens mais pas les chats",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should create a new punctual driver ad entity', async () => {
|
it('should create a new punctual driver ad entity', async () => {
|
||||||
const punctualDriverAd: AdEntity = AdEntity.create(
|
const punctualDriverAd: AdEntity = AdEntity.create(
|
||||||
@@ -191,3 +200,33 @@ describe('Ad entity create', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Ad entity validate status', () => {
|
||||||
|
it('should validate status of a pending ad entity', async () => {
|
||||||
|
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||||
|
punctualPassengerCreateAdProps,
|
||||||
|
);
|
||||||
|
punctualPassengerAd.valid();
|
||||||
|
expect(punctualPassengerAd.getProps().status).toBe(Status.VALID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Ad entity invalidate status', () => {
|
||||||
|
it('should invalidate status of a pending ad entity', async () => {
|
||||||
|
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||||
|
punctualPassengerCreateAdProps,
|
||||||
|
);
|
||||||
|
punctualPassengerAd.invalid();
|
||||||
|
expect(punctualPassengerAd.getProps().status).toBe(Status.INVALID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Ad entity suspend status', () => {
|
||||||
|
it('should suspend status of a pending ad entity', async () => {
|
||||||
|
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||||
|
punctualPassengerCreateAdProps,
|
||||||
|
);
|
||||||
|
punctualPassengerAd.suspend();
|
||||||
|
expect(punctualPassengerAd.getProps().status).toBe(Status.SUSPENDED);
|
||||||
|
});
|
||||||
|
});
|
||||||
105
tests/unit/ad/core/find-ads-by-ids.query-handler.spec.ts
Normal file
105
tests/unit/ad/core/find-ads-by-ids.query-handler.spec.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { FindAdsByIdsQueryHandler } from '@modules/ad/core/application/queries/find-ads-by-ids/find-ads-by-ids.query-handler';
|
||||||
|
import { FindAdsByIdsQuery } from '@modules/ad/core/application/queries/find-ads-by-ids/find-ads-by-ids.query';
|
||||||
|
|
||||||
|
const originWaypointProps: WaypointProps = {
|
||||||
|
position: 0,
|
||||||
|
address: {
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const destinationWaypointProps: WaypointProps = {
|
||||||
|
position: 1,
|
||||||
|
address: {
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const baseCreateAdProps = {
|
||||||
|
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
strict: false,
|
||||||
|
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||||
|
};
|
||||||
|
const punctualCreateAdProps = {
|
||||||
|
fromDate: '2023-06-22',
|
||||||
|
toDate: '2023-06-22',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '08:30',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
};
|
||||||
|
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||||
|
...baseCreateAdProps,
|
||||||
|
...punctualCreateAdProps,
|
||||||
|
driver: false,
|
||||||
|
passenger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ads: AdEntity[] = [
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockAdRepository = {
|
||||||
|
findAllByIds: jest.fn().mockImplementation(() => ads),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Find Ads By Ids Query Handler', () => {
|
||||||
|
let findAdsByIdsQueryHandler: FindAdsByIdsQueryHandler;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: AD_REPOSITORY,
|
||||||
|
useValue: mockAdRepository,
|
||||||
|
},
|
||||||
|
FindAdsByIdsQueryHandler,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
findAdsByIdsQueryHandler = module.get<FindAdsByIdsQueryHandler>(
|
||||||
|
FindAdsByIdsQueryHandler,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(findAdsByIdsQueryHandler).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
it('should return an ad', async () => {
|
||||||
|
const findAdsByIdsQuery = new FindAdsByIdsQuery([
|
||||||
|
'dd264806-13b4-4226-9b18-87adf0ad5dd1',
|
||||||
|
'dd264806-13b4-4226-9b18-87adf0ad5dd2',
|
||||||
|
'dd264806-13b4-4226-9b18-87adf0ad5dd3',
|
||||||
|
]);
|
||||||
|
const ads: AdEntity[] =
|
||||||
|
await findAdsByIdsQueryHandler.execute(findAdsByIdsQuery);
|
||||||
|
expect(ads).toHaveLength(3);
|
||||||
|
expect(ads[1].getProps().fromDate).toBe('2023-06-22');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
103
tests/unit/ad/core/find-ads-by-user-id.query-handler.spec.ts
Normal file
103
tests/unit/ad/core/find-ads-by-user-id.query-handler.spec.ts
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { FindAdsByUserIdQueryHandler } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query-handler';
|
||||||
|
import { FindAdsByUserIdQuery } from '@modules/ad/core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query';
|
||||||
|
|
||||||
|
const originWaypointProps: WaypointProps = {
|
||||||
|
position: 0,
|
||||||
|
address: {
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const destinationWaypointProps: WaypointProps = {
|
||||||
|
position: 1,
|
||||||
|
address: {
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const baseCreateAdProps = {
|
||||||
|
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
strict: false,
|
||||||
|
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||||
|
};
|
||||||
|
const punctualCreateAdProps = {
|
||||||
|
fromDate: '2023-06-22',
|
||||||
|
toDate: '2023-06-22',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '08:30',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
};
|
||||||
|
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||||
|
...baseCreateAdProps,
|
||||||
|
...punctualCreateAdProps,
|
||||||
|
driver: false,
|
||||||
|
passenger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ads: AdEntity[] = [
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
AdEntity.create(punctualPassengerCreateAdProps),
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockAdRepository = {
|
||||||
|
findAll: jest.fn().mockImplementation(() => ads),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Find Ads By User Id Query Handler', () => {
|
||||||
|
let findAdsByUserIdQueryHandler: FindAdsByUserIdQueryHandler;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: AD_REPOSITORY,
|
||||||
|
useValue: mockAdRepository,
|
||||||
|
},
|
||||||
|
FindAdsByUserIdQueryHandler,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
findAdsByUserIdQueryHandler = module.get<FindAdsByUserIdQueryHandler>(
|
||||||
|
FindAdsByUserIdQueryHandler,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(findAdsByUserIdQueryHandler).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
it('should return an ad', async () => {
|
||||||
|
const findAdsByIdsQuery = new FindAdsByUserIdQuery(
|
||||||
|
'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||||
|
);
|
||||||
|
const ads: AdEntity[] =
|
||||||
|
await findAdsByUserIdQueryHandler.execute(findAdsByIdsQuery);
|
||||||
|
expect(ads).toHaveLength(3);
|
||||||
|
expect(ads[1].getProps().fromDate).toBe('2023-06-22');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
98
tests/unit/ad/core/invalidate-ad.service.spec.ts
Normal file
98
tests/unit/ad/core/invalidate-ad.service.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AggregateID } from '@mobicoop/ddd-library';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
import { InvalidateAdService } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.service';
|
||||||
|
import { InvalidateAdCommand } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.command';
|
||||||
|
|
||||||
|
const originWaypointProps: WaypointProps = {
|
||||||
|
position: 0,
|
||||||
|
address: {
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const destinationWaypointProps: WaypointProps = {
|
||||||
|
position: 1,
|
||||||
|
address: {
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const baseCreateAdProps = {
|
||||||
|
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
strict: false,
|
||||||
|
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||||
|
};
|
||||||
|
const punctualCreateAdProps = {
|
||||||
|
fromDate: '2023-06-22',
|
||||||
|
toDate: '2023-06-22',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '08:30',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
};
|
||||||
|
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||||
|
...baseCreateAdProps,
|
||||||
|
...punctualCreateAdProps,
|
||||||
|
driver: false,
|
||||||
|
passenger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
|
||||||
|
|
||||||
|
const mockAdRepository = {
|
||||||
|
findOneById: jest.fn().mockImplementation(() => ad),
|
||||||
|
update: jest.fn().mockImplementation(() => ad.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Invalidate Ad Service', () => {
|
||||||
|
let invalidateAdService: InvalidateAdService;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: AD_REPOSITORY,
|
||||||
|
useValue: mockAdRepository,
|
||||||
|
},
|
||||||
|
InvalidateAdService,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
invalidateAdService = module.get<InvalidateAdService>(InvalidateAdService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(invalidateAdService).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
it('should invalidate an ad', async () => {
|
||||||
|
jest.spyOn(ad, 'invalid');
|
||||||
|
const invalidateAdCommand = new InvalidateAdCommand(ad.id);
|
||||||
|
const result: AggregateID =
|
||||||
|
await invalidateAdService.execute(invalidateAdCommand);
|
||||||
|
expect(result).toBe(ad.id);
|
||||||
|
expect(ad.invalid).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
|
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
|
||||||
import { AdCreatedDomainEvent } from '@modules/ad/core/domain/events/ad-created.domain-events';
|
import { AdCreatedDomainEvent } from '@modules/ad/core/domain/events/ad-created.domain-event';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
|
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
|
||||||
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';
|
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';
|
||||||
98
tests/unit/ad/core/validate-ad.service.spec.ts
Normal file
98
tests/unit/ad/core/validate-ad.service.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||||
|
import { AggregateID } from '@mobicoop/ddd-library';
|
||||||
|
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||||
|
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { ValidateAdService } from '@modules/ad/core/application/commands/validate-ad/validate-ad.service';
|
||||||
|
import { ValidateAdCommand } from '@modules/ad/core/application/commands/validate-ad/validate-ad.command';
|
||||||
|
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||||
|
|
||||||
|
const originWaypointProps: WaypointProps = {
|
||||||
|
position: 0,
|
||||||
|
address: {
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const destinationWaypointProps: WaypointProps = {
|
||||||
|
position: 1,
|
||||||
|
address: {
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
coordinates: {
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const baseCreateAdProps = {
|
||||||
|
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
strict: false,
|
||||||
|
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||||
|
};
|
||||||
|
const punctualCreateAdProps = {
|
||||||
|
fromDate: '2023-06-22',
|
||||||
|
toDate: '2023-06-22',
|
||||||
|
schedule: [
|
||||||
|
{
|
||||||
|
time: '08:30',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
};
|
||||||
|
const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||||
|
...baseCreateAdProps,
|
||||||
|
...punctualCreateAdProps,
|
||||||
|
driver: false,
|
||||||
|
passenger: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
|
||||||
|
|
||||||
|
const mockAdRepository = {
|
||||||
|
findOneById: jest.fn().mockImplementation(() => ad),
|
||||||
|
update: jest.fn().mockImplementation(() => ad.id),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Validate Ad Service', () => {
|
||||||
|
let validateAdService: ValidateAdService;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: AD_REPOSITORY,
|
||||||
|
useValue: mockAdRepository,
|
||||||
|
},
|
||||||
|
ValidateAdService,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
validateAdService = module.get<ValidateAdService>(ValidateAdService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(validateAdService).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('execution', () => {
|
||||||
|
it('should validate an ad', async () => {
|
||||||
|
jest.spyOn(ad, 'valid');
|
||||||
|
const validateAdCommand = new ValidateAdCommand(ad.id);
|
||||||
|
const result: AggregateID =
|
||||||
|
await validateAdService.execute(validateAdCommand);
|
||||||
|
expect(result).toBe(ad.id);
|
||||||
|
expect(ad.valid).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
132
tests/unit/ad/interface/find-ads-by-ids.grpc.controller.spec.ts
Normal file
132
tests/unit/ad/interface/find-ads-by-ids.grpc.controller.spec.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { FindAdsByIdsGrpcController } from '@modules/ad/interface/grpc-controllers/find-ads-by-ids.grpc.controller';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const mockQueryBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => [
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d2',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d3',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d4',
|
||||||
|
])
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockAdMapper = {
|
||||||
|
toResponse: jest.fn().mockImplementationOnce(() => ({
|
||||||
|
userId: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
|
||||||
|
driver: true,
|
||||||
|
passenger: true,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
fromDate: '2023-06-27',
|
||||||
|
toDate: '2023-06-27',
|
||||||
|
schedule: {
|
||||||
|
tue: '07:15',
|
||||||
|
},
|
||||||
|
marginDurations: {
|
||||||
|
mon: 900,
|
||||||
|
tue: 900,
|
||||||
|
wed: 900,
|
||||||
|
thu: 900,
|
||||||
|
fri: 900,
|
||||||
|
sat: 900,
|
||||||
|
sun: 900,
|
||||||
|
},
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
waypoints: [
|
||||||
|
{
|
||||||
|
position: 0,
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: 1,
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Find Ads By Ids Grpc Controller', () => {
|
||||||
|
let findAdsByIdsGrpcController: FindAdsByIdsGrpcController;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: QueryBus,
|
||||||
|
useValue: mockQueryBus,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AdMapper,
|
||||||
|
useValue: mockAdMapper,
|
||||||
|
},
|
||||||
|
FindAdsByIdsGrpcController,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
findAdsByIdsGrpcController = module.get<FindAdsByIdsGrpcController>(
|
||||||
|
FindAdsByIdsGrpcController,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(findAdsByIdsGrpcController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return ads', async () => {
|
||||||
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
|
jest.spyOn(mockAdMapper, 'toResponse');
|
||||||
|
const response = await findAdsByIdsGrpcController.findAllByIds({
|
||||||
|
ids: [
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d2',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d3',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d4',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(response.ads).toHaveLength(3);
|
||||||
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a generic RpcException', async () => {
|
||||||
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
|
jest.spyOn(mockAdMapper, 'toResponse');
|
||||||
|
expect.assertions(4);
|
||||||
|
try {
|
||||||
|
await findAdsByIdsGrpcController.findAllByIds({
|
||||||
|
ids: [
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d2',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d3',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d4',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
}
|
||||||
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
|
import { AdMapper } from '@modules/ad/ad.mapper';
|
||||||
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
|
import { FindAdsByUserIdGrpcController } from '@modules/ad/interface/grpc-controllers/find-ads-by-user-id.grpc.controller';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const mockQueryBus = {
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementationOnce(() => [
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d2',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d3',
|
||||||
|
'200d61a8-d878-4378-a609-c19ea71633d4',
|
||||||
|
])
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error();
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockAdMapper = {
|
||||||
|
toResponse: jest.fn().mockImplementationOnce(() => ({
|
||||||
|
userId: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
|
||||||
|
driver: true,
|
||||||
|
passenger: true,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
fromDate: '2023-06-27',
|
||||||
|
toDate: '2023-06-27',
|
||||||
|
schedule: {
|
||||||
|
tue: '07:15',
|
||||||
|
},
|
||||||
|
marginDurations: {
|
||||||
|
mon: 900,
|
||||||
|
tue: 900,
|
||||||
|
wed: 900,
|
||||||
|
thu: 900,
|
||||||
|
fri: 900,
|
||||||
|
sat: 900,
|
||||||
|
sun: 900,
|
||||||
|
},
|
||||||
|
seatsProposed: 3,
|
||||||
|
seatsRequested: 1,
|
||||||
|
waypoints: [
|
||||||
|
{
|
||||||
|
position: 0,
|
||||||
|
lat: 48.689445,
|
||||||
|
lon: 6.17651,
|
||||||
|
houseNumber: '5',
|
||||||
|
street: 'Avenue Foch',
|
||||||
|
locality: 'Nancy',
|
||||||
|
postalCode: '54000',
|
||||||
|
country: 'France',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: 1,
|
||||||
|
lat: 48.8566,
|
||||||
|
lon: 2.3522,
|
||||||
|
locality: 'Paris',
|
||||||
|
postalCode: '75000',
|
||||||
|
country: 'France',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Find Ads By User Id Grpc Controller', () => {
|
||||||
|
let findAdsByUserIdGrpcController: FindAdsByUserIdGrpcController;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: QueryBus,
|
||||||
|
useValue: mockQueryBus,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AdMapper,
|
||||||
|
useValue: mockAdMapper,
|
||||||
|
},
|
||||||
|
FindAdsByUserIdGrpcController,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
findAdsByUserIdGrpcController = module.get<FindAdsByUserIdGrpcController>(
|
||||||
|
FindAdsByUserIdGrpcController,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(findAdsByUserIdGrpcController).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return ads', async () => {
|
||||||
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
|
jest.spyOn(mockAdMapper, 'toResponse');
|
||||||
|
const response = await findAdsByUserIdGrpcController.findAllByUserId({
|
||||||
|
id: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
|
||||||
|
});
|
||||||
|
expect(response.ads).toHaveLength(3);
|
||||||
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw a generic RpcException', async () => {
|
||||||
|
jest.spyOn(mockQueryBus, 'execute');
|
||||||
|
jest.spyOn(mockAdMapper, 'toResponse');
|
||||||
|
expect.assertions(4);
|
||||||
|
try {
|
||||||
|
await findAdsByUserIdGrpcController.findAllByUserId({
|
||||||
|
id: '8cc90d1a-4a59-4289-a7d8-078f9db7857f',
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
expect(e).toBeInstanceOf(RpcException);
|
||||||
|
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
||||||
|
}
|
||||||
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { MatcherAdCreatedMessageHandler } from '@modules/ad/interface/message-handlers/matcher-ad-created.message-handler';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const matcherAdCreatedMessage =
|
||||||
|
'{"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4","driverDuration":"3512","driverDistance":"65845","fwdAzimuth":"90","backAzimuth":"270"}';
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Matcher Ad Created Message Handler', () => {
|
||||||
|
let matcherAdCreatedMessageHandler: MatcherAdCreatedMessageHandler;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
MatcherAdCreatedMessageHandler,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
matcherAdCreatedMessageHandler = module.get<MatcherAdCreatedMessageHandler>(
|
||||||
|
MatcherAdCreatedMessageHandler,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(matcherAdCreatedMessageHandler).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should validate an ad', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await matcherAdCreatedMessageHandler.matcherAdCreated(
|
||||||
|
matcherAdCreatedMessage,
|
||||||
|
);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { MatcherAdCreationFailedMessageHandler } from '@modules/ad/interface/message-handlers/matcher-ad-creation-failed.message-handler';
|
||||||
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
|
const matcherAdCreationFailedMessage =
|
||||||
|
'{"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4"}';
|
||||||
|
|
||||||
|
const mockCommandBus = {
|
||||||
|
execute: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Matcher Ad Creation Failed Message Handler', () => {
|
||||||
|
let matcherAdCreationFailedMessageHandler: MatcherAdCreationFailedMessageHandler;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CommandBus,
|
||||||
|
useValue: mockCommandBus,
|
||||||
|
},
|
||||||
|
MatcherAdCreationFailedMessageHandler,
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
matcherAdCreationFailedMessageHandler =
|
||||||
|
module.get<MatcherAdCreationFailedMessageHandler>(
|
||||||
|
MatcherAdCreationFailedMessageHandler,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(matcherAdCreationFailedMessageHandler).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should invalidate an ad', async () => {
|
||||||
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
await matcherAdCreationFailedMessageHandler.matcherAdCreationFailed(
|
||||||
|
matcherAdCreationFailedMessage,
|
||||||
|
);
|
||||||
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"@libs/*": ["src/libs/*"],
|
"@libs/*": ["src/libs/*"],
|
||||||
"@modules/*": ["src/modules/*"],
|
"@modules/*": ["src/modules/*"],
|
||||||
"@src/*": ["src/*"]
|
"@src/*": ["src/*"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user