Merge branch 'healthUpgrade' into 'main'
upgrade health, use configuration and broker packages See merge request v3/service/ad!7
This commit is contained in:
		
						commit
						cd84567107
					
				
							
								
								
									
										10
									
								
								.env.dist
								
								
								
								
							
							
						
						
									
										10
									
								
								.env.dist
								
								
								
								
							| 
						 | 
				
			
			@ -7,9 +7,9 @@ HEALTH_SERVICE_PORT=6006
 | 
			
		|||
# PRISMA
 | 
			
		||||
DATABASE_URL="postgresql://mobicoop:mobicoop@v3-db:5432/mobicoop?schema=ad"
 | 
			
		||||
 | 
			
		||||
# RABBIT MQ
 | 
			
		||||
RMQ_URI=amqp://v3-broker:5672
 | 
			
		||||
RMQ_EXCHANGE=mobicoop
 | 
			
		||||
# MESSAGE BROKER
 | 
			
		||||
MESSAGE_BROKER_URI=amqp://v3-broker:5672
 | 
			
		||||
MESSAGE_BROKER_EXCHANGE=mobicoop
 | 
			
		||||
 | 
			
		||||
# REDIS
 | 
			
		||||
REDIS_HOST=v3-redis
 | 
			
		||||
| 
						 | 
				
			
			@ -26,5 +26,5 @@ ROLE=passenger
 | 
			
		|||
SEATS_PROVIDED=3
 | 
			
		||||
SEATS_REQUESTED=1
 | 
			
		||||
 | 
			
		||||
# ACCEPT ONLY SAME FREQUENCY REQUESTS 
 | 
			
		||||
STRICT_FREQUENCY=false 
 | 
			
		||||
# ACCEPT ONLY SAME FREQUENCY REQUESTS
 | 
			
		||||
STRICT_FREQUENCY=false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										115
									
								
								README.md
								
								
								
								
							
							
						
						
									
										115
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -58,22 +58,18 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
			
		|||
 | 
			
		||||
-   **Create** : create an ad (note that uuid is optional, a uuid will be automatically attributed if it is not provided)
 | 
			
		||||
 | 
			
		||||
    Punctual driver ad :
 | 
			
		||||
 | 
			
		||||
    ```json
 | 
			
		||||
    {
 | 
			
		||||
        "userUuid": "113e0000-0000-4000-a000-000000000000",
 | 
			
		||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
			
		||||
        "driver": true,
 | 
			
		||||
        "passenger": false,
 | 
			
		||||
        "frequency": "PUNCTUAL",
 | 
			
		||||
        "departure": "2023-01-15",
 | 
			
		||||
        "toDate": "2023-08-01",
 | 
			
		||||
 | 
			
		||||
        "marginDurations": {
 | 
			
		||||
            "mon": 800
 | 
			
		||||
        },
 | 
			
		||||
        "seatsPassenger": 0,
 | 
			
		||||
        "seatsDriver": 3,
 | 
			
		||||
        "frequency": "PUNCTUAL",
 | 
			
		||||
        "departure": "2023-01-15 09:00",
 | 
			
		||||
        "addresses": [
 | 
			
		||||
            {
 | 
			
		||||
                "position": 0,
 | 
			
		||||
                "lon": 48.68944505415954,
 | 
			
		||||
                "lat": 6.176510296462267,
 | 
			
		||||
                "houseNumber": "5",
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +79,7 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
			
		|||
                "country": "France"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "position": 1,
 | 
			
		||||
                "lon": 48.8566,
 | 
			
		||||
                "lat": 2.3522,
 | 
			
		||||
                "locality": "Paris",
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +90,104 @@ The app exposes the following [gRPC](https://grpc.io/) services :
 | 
			
		|||
    }
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    Punctual driver and passenger ad :
 | 
			
		||||
 | 
			
		||||
    ```json
 | 
			
		||||
    {
 | 
			
		||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
			
		||||
        "driver": true,
 | 
			
		||||
        "pasenger": true,
 | 
			
		||||
        "seatsDriver": 3,
 | 
			
		||||
        "seatsPassenger": 1,
 | 
			
		||||
        "frequency": "PUNCTUAL",
 | 
			
		||||
        "departure": "2023-01-15 09:00",
 | 
			
		||||
        "addresses": [
 | 
			
		||||
            {
 | 
			
		||||
                "position": 0,
 | 
			
		||||
                "lon": 48.68944505415954,
 | 
			
		||||
                "lat": 6.176510296462267,
 | 
			
		||||
                "houseNumber": "5",
 | 
			
		||||
                "street": "Avenue Foch",
 | 
			
		||||
                "locality": "Nancy",
 | 
			
		||||
                "postalCode": "54000",
 | 
			
		||||
                "country": "France"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "position": 1,
 | 
			
		||||
                "lon": 48.8566,
 | 
			
		||||
                "lat": 2.3522,
 | 
			
		||||
                "locality": "Paris",
 | 
			
		||||
                "postalCode": "75000",
 | 
			
		||||
                "country": "France"
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    Recurrent passenger ad :
 | 
			
		||||
 | 
			
		||||
    ```json
 | 
			
		||||
    {
 | 
			
		||||
        "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245",
 | 
			
		||||
        "passenger": true,
 | 
			
		||||
        "seatsPassenger": 1,
 | 
			
		||||
        "frequency": "RECURRRENT",
 | 
			
		||||
        "fromDate": "2023-01-15",
 | 
			
		||||
        "toDate": "2023-12-31",
 | 
			
		||||
        "schedule": {
 | 
			
		||||
            "mon": "07:00",
 | 
			
		||||
            "tue": "07:05",
 | 
			
		||||
            "fri": "07:10"
 | 
			
		||||
        },
 | 
			
		||||
        "addresses": [
 | 
			
		||||
            {
 | 
			
		||||
                "position": 0,
 | 
			
		||||
                "lon": 48.68944505415954,
 | 
			
		||||
                "lat": 6.176510296462267,
 | 
			
		||||
                "houseNumber": "5",
 | 
			
		||||
                "street": "Avenue Foch",
 | 
			
		||||
                "locality": "Nancy",
 | 
			
		||||
                "postalCode": "54000",
 | 
			
		||||
                "country": "France"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "position": 1,
 | 
			
		||||
                "lon": 48.8566,
 | 
			
		||||
                "lat": 2.3522,
 | 
			
		||||
                "locality": "Paris",
 | 
			
		||||
                "postalCode": "75000",
 | 
			
		||||
                "country": "France"
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
    The list of possible options when creating an ad :
 | 
			
		||||
 | 
			
		||||
    -   uuid (optional): the uuid of the ad
 | 
			
		||||
    -   userUuid: the user uuid
 | 
			
		||||
    -   driver (boolean, optional): if the ad is a driver ad
 | 
			
		||||
    -   passenger (boolean, optional): if the ad is a passenger ad
 | 
			
		||||
    -   frequency: `PUNCTUAL` or `RECURRENT`
 | 
			
		||||
    -   departure (required if punctual): departure date and hour/minute for a punctual ad
 | 
			
		||||
    -   fromDate (required if recurrent): start date for recurrent ad
 | 
			
		||||
    -   toDate (required if recurrent): end date for recurrent ad
 | 
			
		||||
    -   schedule (required if recurrent): an object with the departure time for each carpooled day in the week
 | 
			
		||||
    -   marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg:
 | 
			
		||||
 | 
			
		||||
            {
 | 
			
		||||
                "mon": 900,
 | 
			
		||||
                "tue": 850,
 | 
			
		||||
                "fri": 950
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
    -   seatsDriver (optional): number of seats proposed as driver;
 | 
			
		||||
    -   seatsPassenger (optional): number of seats requested as passenger;
 | 
			
		||||
    -   strict (boolean, optional): if set to true, allow matching only with similar frequency ads
 | 
			
		||||
    -   addresses: an array of adresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads)
 | 
			
		||||
 | 
			
		||||
    Default values must be set in `.env` file.
 | 
			
		||||
 | 
			
		||||
## Messages
 | 
			
		||||
 | 
			
		||||
As mentionned earlier, RabbitMQ messages are sent after these events :
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,10 +12,11 @@
 | 
			
		|||
        "@automapper/classes": "^8.7.7",
 | 
			
		||||
        "@automapper/core": "^8.7.7",
 | 
			
		||||
        "@automapper/nestjs": "^8.7.7",
 | 
			
		||||
        "@golevelup/nestjs-rabbitmq": "^3.6.0",
 | 
			
		||||
        "@grpc/grpc-js": "^1.8.14",
 | 
			
		||||
        "@grpc/proto-loader": "^0.7.6",
 | 
			
		||||
        "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
			
		||||
        "@mobicoop/configuration-module": "^1.1.0",
 | 
			
		||||
        "@mobicoop/message-broker-module": "^1.0.5",
 | 
			
		||||
        "@nestjs/common": "^9.0.0",
 | 
			
		||||
        "@nestjs/config": "^2.3.1",
 | 
			
		||||
        "@nestjs/core": "^9.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +57,24 @@
 | 
			
		|||
        "typescript": "^4.7.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@acuminous/bitsyntax": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "buffer-more-ints": "~1.0.0",
 | 
			
		||||
        "debug": "^4.3.4",
 | 
			
		||||
        "safe-buffer": "~5.1.2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": {
 | 
			
		||||
      "version": "5.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@ampproject/remapping": {
 | 
			
		||||
      "version": "2.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1548,6 +1567,106 @@
 | 
			
		|||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/configuration-module": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mobicoop/configuration-module/-/configuration-module-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-4yzCrY8m40XOO3CZnWJC4kHk66sTQCwe5UjKCV/UpNkN9IGUKW+R84J/53aulmGTL95vec7g6tFIwlHJd9BCoA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
			
		||||
        "@mobicoop/message-broker-module": "^1.0.4",
 | 
			
		||||
        "@nestjs/cqrs": "^9.0.4",
 | 
			
		||||
        "@types/amqplib": "^0.10.1",
 | 
			
		||||
        "amqplib": "^0.10.3",
 | 
			
		||||
        "class-validator": "^0.14.0",
 | 
			
		||||
        "ioredis": "^5.3.2"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@nestjs/common": "^9.4.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/configuration-module/node_modules/amqplib": {
 | 
			
		||||
      "version": "0.10.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz",
 | 
			
		||||
      "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@acuminous/bitsyntax": "^0.1.2",
 | 
			
		||||
        "buffer-more-ints": "~1.0.0",
 | 
			
		||||
        "readable-stream": "1.x >=1.1.9",
 | 
			
		||||
        "url-parse": "~1.5.10"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/configuration-module/node_modules/isarray": {
 | 
			
		||||
      "version": "0.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/configuration-module/node_modules/readable-stream": {
 | 
			
		||||
      "version": "1.1.14",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
 | 
			
		||||
      "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "core-util-is": "~1.0.0",
 | 
			
		||||
        "inherits": "~2.0.1",
 | 
			
		||||
        "isarray": "0.0.1",
 | 
			
		||||
        "string_decoder": "~0.10.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/configuration-module/node_modules/string_decoder": {
 | 
			
		||||
      "version": "0.10.31",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
 | 
			
		||||
      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/message-broker-module": {
 | 
			
		||||
      "version": "1.0.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mobicoop/message-broker-module/-/message-broker-module-1.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-aVkWErc5pHz1oPRVBzvK3CvKKcUSNDvW58fbFXHbOA+md+jnyP9sH9NHyIOtVzIv0f6DbJBn9SA3x4VnSrDaBg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@golevelup/nestjs-rabbitmq": "^3.6.0",
 | 
			
		||||
        "@types/amqplib": "^0.10.1",
 | 
			
		||||
        "amqplib": "^0.10.3"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@nestjs/common": "^9.4.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/message-broker-module/node_modules/amqplib": {
 | 
			
		||||
      "version": "0.10.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz",
 | 
			
		||||
      "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@acuminous/bitsyntax": "^0.1.2",
 | 
			
		||||
        "buffer-more-ints": "~1.0.0",
 | 
			
		||||
        "readable-stream": "1.x >=1.1.9",
 | 
			
		||||
        "url-parse": "~1.5.10"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/message-broker-module/node_modules/isarray": {
 | 
			
		||||
      "version": "0.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/message-broker-module/node_modules/readable-stream": {
 | 
			
		||||
      "version": "1.1.14",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
 | 
			
		||||
      "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "core-util-is": "~1.0.0",
 | 
			
		||||
        "inherits": "~2.0.1",
 | 
			
		||||
        "isarray": "0.0.1",
 | 
			
		||||
        "string_decoder": "~0.10.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mobicoop/message-broker-module/node_modules/string_decoder": {
 | 
			
		||||
      "version": "0.10.31",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
 | 
			
		||||
      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/cli": {
 | 
			
		||||
      "version": "9.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.4.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1632,12 +1751,12 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/common": {
 | 
			
		||||
      "version": "9.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==",
 | 
			
		||||
      "version": "9.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-sea+qZnbD5x3YWZDVQT/wbVJ2NiABaM1tyZTLuW9hpkcM2KFA96xKtK3VaCxyz49zoXIgSOefsyK7HuUMCe27Q==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "iterare": "1.2.1",
 | 
			
		||||
        "tslib": "2.5.0",
 | 
			
		||||
        "tslib": "2.5.2",
 | 
			
		||||
        "uid": "2.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
| 
						 | 
				
			
			@ -1663,6 +1782,11 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/common/node_modules/tslib": {
 | 
			
		||||
      "version": "2.5.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
 | 
			
		||||
      "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/config": {
 | 
			
		||||
      "version": "2.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.3.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1717,9 +1841,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nestjs/cqrs": {
 | 
			
		||||
      "version": "9.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-9.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-hmbrqf51BVdgmnnxErnLVXfPNTEqr4Hz8DyLa9dKLIW3BuOyI5RDwJ/9sKbJ47UDBhumC5nQlNK9qk27mhqHfw==",
 | 
			
		||||
      "version": "9.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-9.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-nWDF+xs4jqs6OjxFg/wVSd0NiIV9+EFCJrJNTo4VRWe78CcAaitbp56CBspUh4gKyfkci95i+EhHdEqRXKFptg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "uuid": "9.0.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -2162,6 +2286,14 @@
 | 
			
		|||
      "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/amqplib": {
 | 
			
		||||
      "version": "0.10.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.1.tgz",
 | 
			
		||||
      "integrity": "sha512-j6ANKT79ncUDnAs/+9r9eDujxbeJoTjoVu33gHHcaPfmLQaMhvfbH2GqSe8KUM444epAp1Vl3peVOQfZk3UIqA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/node": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/babel__core": {
 | 
			
		||||
      "version": "7.20.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,10 +37,11 @@
 | 
			
		|||
    "@automapper/classes": "^8.7.7",
 | 
			
		||||
    "@automapper/core": "^8.7.7",
 | 
			
		||||
    "@automapper/nestjs": "^8.7.7",
 | 
			
		||||
    "@golevelup/nestjs-rabbitmq": "^3.6.0",
 | 
			
		||||
    "@grpc/grpc-js": "^1.8.14",
 | 
			
		||||
    "@grpc/proto-loader": "^0.7.6",
 | 
			
		||||
    "@liaoliaots/nestjs-redis": "^9.0.5",
 | 
			
		||||
    "@mobicoop/configuration-module": "^1.1.0",
 | 
			
		||||
    "@mobicoop/message-broker-module": "^1.0.5",
 | 
			
		||||
    "@nestjs/common": "^9.0.0",
 | 
			
		||||
    "@nestjs/config": "^2.3.1",
 | 
			
		||||
    "@nestjs/core": "^9.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +94,7 @@
 | 
			
		|||
      ".presenter.ts",
 | 
			
		||||
      ".profile.ts",
 | 
			
		||||
      ".exception.ts",
 | 
			
		||||
      ".constants.ts",
 | 
			
		||||
      "main.ts"
 | 
			
		||||
    ],
 | 
			
		||||
    "rootDir": "src",
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +113,8 @@
 | 
			
		|||
      ".presenter.ts",
 | 
			
		||||
      ".profile.ts",
 | 
			
		||||
      ".exception.ts",
 | 
			
		||||
      ".constants.ts",
 | 
			
		||||
      ".interfaces.ts",
 | 
			
		||||
      "main.ts"
 | 
			
		||||
    ],
 | 
			
		||||
    "coverageDirectory": "../coverage",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
export const MESSAGE_BROKER_PUBLISHER = Symbol();
 | 
			
		||||
export const MESSAGE_PUBLISHER = Symbol();
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +1,65 @@
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { ConfigModule } from '@nestjs/config';
 | 
			
		||||
import { ConfigurationModule } from './modules/configuration/configuration.module';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { HealthModule } from './modules/health/health.module';
 | 
			
		||||
import { AdModule } from './modules/ad/ad.module';
 | 
			
		||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
			
		||||
import { classes } from '@automapper/classes';
 | 
			
		||||
import {
 | 
			
		||||
  MessageBrokerModule,
 | 
			
		||||
  MessageBrokerModuleOptions,
 | 
			
		||||
} from '@mobicoop/message-broker-module';
 | 
			
		||||
import {
 | 
			
		||||
  ConfigurationModule,
 | 
			
		||||
  ConfigurationModuleOptions,
 | 
			
		||||
} from '@mobicoop/configuration-module';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    ConfigModule.forRoot({ isGlobal: true }),
 | 
			
		||||
    AutomapperModule.forRoot({ strategyInitializer: classes() }),
 | 
			
		||||
    ConfigurationModule,
 | 
			
		||||
    MessageBrokerModule.forRootAsync(
 | 
			
		||||
      {
 | 
			
		||||
        imports: [ConfigModule],
 | 
			
		||||
        inject: [ConfigService],
 | 
			
		||||
        useFactory: async (
 | 
			
		||||
          configService: ConfigService,
 | 
			
		||||
        ): Promise<MessageBrokerModuleOptions> => ({
 | 
			
		||||
          uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
			
		||||
          exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
			
		||||
        }),
 | 
			
		||||
      },
 | 
			
		||||
      false,
 | 
			
		||||
    ),
 | 
			
		||||
    ConfigurationModule.forRootAsync(
 | 
			
		||||
      {
 | 
			
		||||
        imports: [ConfigModule],
 | 
			
		||||
        inject: [ConfigService],
 | 
			
		||||
        useFactory: async (
 | 
			
		||||
          configService: ConfigService,
 | 
			
		||||
        ): Promise<ConfigurationModuleOptions> => ({
 | 
			
		||||
          domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
 | 
			
		||||
          messageBroker: {
 | 
			
		||||
            uri: configService.get<string>('MESSAGE_BROKER_URI'),
 | 
			
		||||
            exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
 | 
			
		||||
          },
 | 
			
		||||
          redis: {
 | 
			
		||||
            host: configService.get<string>('REDIS_HOST'),
 | 
			
		||||
            password: configService.get<string>('REDIS_PASSWORD'),
 | 
			
		||||
            port: configService.get<number>('REDIS_PORT'),
 | 
			
		||||
          },
 | 
			
		||||
          setConfigurationBrokerRoutingKeys: [
 | 
			
		||||
            'configuration.create',
 | 
			
		||||
            'configuration.update',
 | 
			
		||||
          ],
 | 
			
		||||
          deleteConfigurationRoutingKey: 'configuration.delete',
 | 
			
		||||
          propagateConfigurationRoutingKey: 'configuration.propagate',
 | 
			
		||||
          setConfigurationBrokerQueue: 'ad-configuration-create-update',
 | 
			
		||||
          deleteConfigurationQueue: 'ad-configuration-delete',
 | 
			
		||||
          propagateConfigurationQueue: 'ad-configuration-propagate',
 | 
			
		||||
        }),
 | 
			
		||||
      },
 | 
			
		||||
      true,
 | 
			
		||||
    ),
 | 
			
		||||
    HealthModule,
 | 
			
		||||
    AdModule,
 | 
			
		||||
  ],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export interface IPublishMessage {
 | 
			
		||||
  publish(routingKey: string, message: string): void;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export const PARAMS_PROVIDER = Symbol();
 | 
			
		||||
| 
						 | 
				
			
			@ -2,45 +2,36 @@ import { Module } from '@nestjs/common';
 | 
			
		|||
import { AdController } from './adapters/primaries/ad.controller';
 | 
			
		||||
import { DatabaseModule } from '../database/database.module';
 | 
			
		||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
			
		||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { AdProfile } from './mappers/ad.profile';
 | 
			
		||||
import { AdsRepository } from './adapters/secondaries/ads.repository';
 | 
			
		||||
import { Messager } from './adapters/secondaries/messager';
 | 
			
		||||
import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase';
 | 
			
		||||
import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
 | 
			
		||||
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
 | 
			
		||||
import { PARAMS_PROVIDER } from './ad.constants';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
			
		||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
			
		||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    DatabaseModule,
 | 
			
		||||
    CqrsModule,
 | 
			
		||||
    RabbitMQModule.forRootAsync(RabbitMQModule, {
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      useFactory: async (configService: ConfigService) => ({
 | 
			
		||||
        exchanges: [
 | 
			
		||||
          {
 | 
			
		||||
            name: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            type: 'topic',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        uri: configService.get<string>('RMQ_URI'),
 | 
			
		||||
        connectionInitOptions: { wait: false },
 | 
			
		||||
      }),
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [DatabaseModule, CqrsModule],
 | 
			
		||||
  controllers: [AdController],
 | 
			
		||||
  providers: [
 | 
			
		||||
    AdProfile,
 | 
			
		||||
    AdsRepository,
 | 
			
		||||
    Messager,
 | 
			
		||||
    FindAdByUuidUseCase,
 | 
			
		||||
    CreateAdUseCase,
 | 
			
		||||
    {
 | 
			
		||||
      provide: 'ParamsProvider',
 | 
			
		||||
      provide: PARAMS_PROVIDER,
 | 
			
		||||
      useClass: DefaultParamsProvider,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      provide: MESSAGE_BROKER_PUBLISHER,
 | 
			
		||||
      useClass: MessageBrokerPublisher,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      provide: MESSAGE_PUBLISHER,
 | 
			
		||||
      useClass: MessagePublisher,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class AdModule {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
			
		||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
			
		||||
import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class MessagePublisher implements IPublishMessage {
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
			
		||||
    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  publish = (routingKey: string, message: string): void => {
 | 
			
		||||
    this.messageBrokerPublisher.publish(routingKey, message);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { IMessageBroker } from '../../domain/interfaces/message-broker';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class Messager extends IMessageBroker {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly amqpConnection: AmqpConnection,
 | 
			
		||||
    configService: ConfigService,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(configService.get<string>('RMQ_EXCHANGE'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  publish(routingKey: string, message: string): void {
 | 
			
		||||
    this.amqpConnection.publish(this.exchange, routingKey, message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export abstract class IMessageBroker {
 | 
			
		||||
  exchange: string;
 | 
			
		||||
 | 
			
		||||
  constructor(exchange: string) {
 | 
			
		||||
    this.exchange = exchange;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract publish(routingKey: string, message: string): void;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,6 @@ import { Mapper } from '@automapper/core';
 | 
			
		|||
import { InjectMapper } from '@automapper/nestjs';
 | 
			
		||||
import { Inject } from '@nestjs/common';
 | 
			
		||||
import { CommandHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { Messager } from '../../adapters/secondaries/messager';
 | 
			
		||||
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
 | 
			
		||||
import { CreateAdCommand } from '../../commands/create-ad.command';
 | 
			
		||||
import { CreateAdRequest } from '../dtos/create-ad.request';
 | 
			
		||||
| 
						 | 
				
			
			@ -10,6 +9,9 @@ import { IProvideParams } from '../interfaces/param-provider.interface';
 | 
			
		|||
import { DefaultParams } from '../types/default-params.type';
 | 
			
		||||
import { AdCreation } from '../dtos/ad.creation';
 | 
			
		||||
import { Ad } from '../entities/ad';
 | 
			
		||||
import { PARAMS_PROVIDER } from '../../ad.constants';
 | 
			
		||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
			
		||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
			
		||||
 | 
			
		||||
@CommandHandler(CreateAdCommand)
 | 
			
		||||
export class CreateAdUseCase {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,9 +19,10 @@ export class CreateAdUseCase {
 | 
			
		|||
  private ad: AdCreation;
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly repository: AdsRepository,
 | 
			
		||||
    private readonly messager: Messager,
 | 
			
		||||
    @Inject(MESSAGE_PUBLISHER)
 | 
			
		||||
    private readonly messagePublisher: IPublishMessage,
 | 
			
		||||
    @InjectMapper() private readonly mapper: Mapper,
 | 
			
		||||
    @Inject('ParamsProvider')
 | 
			
		||||
    @Inject(PARAMS_PROVIDER)
 | 
			
		||||
    private readonly defaultParamsProvider: IProvideParams,
 | 
			
		||||
  ) {
 | 
			
		||||
    this.defaultParams = defaultParamsProvider.getParams();
 | 
			
		||||
| 
						 | 
				
			
			@ -38,8 +41,8 @@ export class CreateAdUseCase {
 | 
			
		|||
 | 
			
		||||
    try {
 | 
			
		||||
      const adCreated: Ad = await this.repository.create(this.ad);
 | 
			
		||||
      this.messager.publish('ad.create', JSON.stringify(adCreated));
 | 
			
		||||
      this.messager.publish(
 | 
			
		||||
      this.messagePublisher.publish('ad.create', JSON.stringify(adCreated));
 | 
			
		||||
      this.messagePublisher.publish(
 | 
			
		||||
        'logging.ad.create.info',
 | 
			
		||||
        JSON.stringify(adCreated),
 | 
			
		||||
      );
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +52,7 @@ export class CreateAdUseCase {
 | 
			
		|||
      if (error.message.includes('Already exists')) {
 | 
			
		||||
        key = 'logging.ad.create.warning';
 | 
			
		||||
      }
 | 
			
		||||
      this.messager.publish(
 | 
			
		||||
      this.messagePublisher.publish(
 | 
			
		||||
        key,
 | 
			
		||||
        JSON.stringify({
 | 
			
		||||
          command,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +1,17 @@
 | 
			
		|||
import { NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { Inject, NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { QueryHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
 | 
			
		||||
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
 | 
			
		||||
import { Messager } from '../../adapters/secondaries/messager';
 | 
			
		||||
import { Ad } from '../entities/ad';
 | 
			
		||||
import { MESSAGE_PUBLISHER } from '../../../../app.constants';
 | 
			
		||||
import { IPublishMessage } from '../../../../interfaces/message-publisher';
 | 
			
		||||
 | 
			
		||||
@QueryHandler(FindAdByUuidQuery)
 | 
			
		||||
export class FindAdByUuidUseCase {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly repository: AdsRepository,
 | 
			
		||||
    private readonly messager: Messager,
 | 
			
		||||
    @Inject(MESSAGE_PUBLISHER)
 | 
			
		||||
    private readonly messagePublisher: IPublishMessage,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  async execute(findAdByUuid: FindAdByUuidQuery): Promise<Ad> {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +20,7 @@ export class FindAdByUuidUseCase {
 | 
			
		|||
      if (!ad) throw new NotFoundException();
 | 
			
		||||
      return ad;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      this.messager.publish(
 | 
			
		||||
      this.messagePublisher.publish(
 | 
			
		||||
        'logging.ad.read.warning',
 | 
			
		||||
        JSON.stringify({
 | 
			
		||||
          query: findAdByUuid,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { MessagePublisher } from '../../../../adapters/secondaries/message-publisher';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../../../app.constants';
 | 
			
		||||
 | 
			
		||||
const mockMessageBrokerPublisher = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Messager', () => {
 | 
			
		||||
  let messagePublisher: MessagePublisher;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      imports: [],
 | 
			
		||||
      providers: [
 | 
			
		||||
        MessagePublisher,
 | 
			
		||||
        {
 | 
			
		||||
          provide: MESSAGE_BROKER_PUBLISHER,
 | 
			
		||||
          useValue: mockMessageBrokerPublisher,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(messagePublisher).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should publish a message', async () => {
 | 
			
		||||
    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
			
		||||
    messagePublisher.publish('health.info', 'my-test');
 | 
			
		||||
    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { Messager } from '../../../../adapters/secondaries/messager';
 | 
			
		||||
 | 
			
		||||
const mockAmqpConnection = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockConfigService = {
 | 
			
		||||
  get: jest.fn().mockResolvedValue({
 | 
			
		||||
    RMQ_EXCHANGE: 'mobicoop',
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Messager', () => {
 | 
			
		||||
  let messager: Messager;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      imports: [],
 | 
			
		||||
      providers: [
 | 
			
		||||
        Messager,
 | 
			
		||||
        {
 | 
			
		||||
          provide: AmqpConnection,
 | 
			
		||||
          useValue: mockAmqpConnection,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: ConfigService,
 | 
			
		||||
          useValue: mockConfigService,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    messager = module.get<Messager>(Messager);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(messager).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should publish a message', async () => {
 | 
			
		||||
    jest.spyOn(mockAmqpConnection, 'publish');
 | 
			
		||||
    messager.publish('ad.create.info', 'my-test');
 | 
			
		||||
    expect(mockAmqpConnection.publish).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { CreateAdUseCase } from '../../../domain/usecases/create-ad.usecase';
 | 
			
		||||
import { CreateAdRequest } from '../../../domain/dtos/create-ad.request';
 | 
			
		||||
import { Messager } from '../../../adapters/secondaries/messager';
 | 
			
		||||
import { AdsRepository } from '../../../adapters/secondaries/ads.repository';
 | 
			
		||||
import { CreateAdCommand } from '../../../commands/create-ad.command';
 | 
			
		||||
import { AutomapperModule } from '@automapper/nestjs';
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +10,9 @@ import { Ad } from '../../../domain/entities/ad';
 | 
			
		|||
import { AdProfile } from '../../../mappers/ad.profile';
 | 
			
		||||
import { AddressDTO } from '../../../domain/dtos/address.dto';
 | 
			
		||||
import { AdCreation } from '../../../domain/dtos/ad.creation';
 | 
			
		||||
import { Address } from 'src/modules/ad/domain/entities/address';
 | 
			
		||||
import { Address } from '../../../domain/entities/address';
 | 
			
		||||
import { PARAMS_PROVIDER } from '../../../ad.constants';
 | 
			
		||||
import { MESSAGE_PUBLISHER } from '../../../../../app.constants';
 | 
			
		||||
 | 
			
		||||
const mockAddress1: AddressDTO = {
 | 
			
		||||
  position: 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +81,7 @@ const newAdRequest: CreateAdRequest = {
 | 
			
		|||
  addresses: [mockAddress1, mockAddress2],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockMessager = {
 | 
			
		||||
const mockMessagePublisher = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
const mockDefaultParamsProvider = {
 | 
			
		||||
| 
						 | 
				
			
			@ -128,13 +129,13 @@ describe('CreateAdUseCase', () => {
 | 
			
		|||
          useValue: mockAdRepository,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: Messager,
 | 
			
		||||
          useValue: mockMessager,
 | 
			
		||||
          provide: MESSAGE_PUBLISHER,
 | 
			
		||||
          useValue: mockMessagePublisher,
 | 
			
		||||
        },
 | 
			
		||||
        CreateAdUseCase,
 | 
			
		||||
        AdProfile,
 | 
			
		||||
        {
 | 
			
		||||
          provide: 'ParamsProvider',
 | 
			
		||||
          provide: PARAMS_PROVIDER,
 | 
			
		||||
          useValue: mockDefaultParamsProvider,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
import { NotFoundException } from '@nestjs/common';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { Messager } from '../../../adapters/secondaries/messager';
 | 
			
		||||
import { FindAdByUuidQuery } from '../../../queries/find-ad-by-uuid.query';
 | 
			
		||||
import { AdsRepository } from '../../../adapters/secondaries/ads.repository';
 | 
			
		||||
import { FindAdByUuidUseCase } from '../../../domain/usecases/find-ad-by-uuid.usecase';
 | 
			
		||||
import { FindAdByUuidRequest } from '../../../domain/dtos/find-ad-by-uuid.request';
 | 
			
		||||
import { MESSAGE_PUBLISHER } from '../../../../../app.constants';
 | 
			
		||||
 | 
			
		||||
const mockAd = {
 | 
			
		||||
  uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ const mockAdRepository = {
 | 
			
		|||
    }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockMessager = {
 | 
			
		||||
const mockMessagePublisher = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -37,8 +37,8 @@ describe('FindAdByUuidUseCase', () => {
 | 
			
		|||
          useValue: mockAdRepository,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: Messager,
 | 
			
		||||
          useValue: mockMessager,
 | 
			
		||||
          provide: MESSAGE_PUBLISHER,
 | 
			
		||||
          useValue: mockMessagePublisher,
 | 
			
		||||
        },
 | 
			
		||||
        FindAdByUuidUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,77 +0,0 @@
 | 
			
		|||
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { Controller } from '@nestjs/common';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { CommandBus } from '@nestjs/cqrs';
 | 
			
		||||
import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
 | 
			
		||||
import { SetConfigurationCommand } from '../../commands/set-configuration.command';
 | 
			
		||||
import { DeleteConfigurationRequest } from '../../domain/dtos/delete-configuration.request';
 | 
			
		||||
import { SetConfigurationRequest } from '../../domain/dtos/set-configuration.request';
 | 
			
		||||
import { Configuration } from '../../domain/entities/configuration';
 | 
			
		||||
 | 
			
		||||
@Controller()
 | 
			
		||||
export class ConfigurationMessagerController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _commandBus: CommandBus,
 | 
			
		||||
    private readonly _configService: ConfigService,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    name: 'setConfiguration',
 | 
			
		||||
  })
 | 
			
		||||
  public async setConfigurationHandler(message: string) {
 | 
			
		||||
    const configuration: Configuration = JSON.parse(message);
 | 
			
		||||
    if (
 | 
			
		||||
      configuration.domain ==
 | 
			
		||||
      this._configService.get<string>('SERVICE_CONFIGURATION_DOMAIN')
 | 
			
		||||
    ) {
 | 
			
		||||
      const setConfigurationRequest: SetConfigurationRequest =
 | 
			
		||||
        new SetConfigurationRequest();
 | 
			
		||||
      setConfigurationRequest.domain = configuration.domain;
 | 
			
		||||
      setConfigurationRequest.key = configuration.key;
 | 
			
		||||
      setConfigurationRequest.value = configuration.value;
 | 
			
		||||
      await this._commandBus.execute(
 | 
			
		||||
        new SetConfigurationCommand(setConfigurationRequest),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    name: 'deleteConfiguration',
 | 
			
		||||
  })
 | 
			
		||||
  public async configurationDeletedHandler(message: string) {
 | 
			
		||||
    const deletedConfiguration: Configuration = JSON.parse(message);
 | 
			
		||||
    if (
 | 
			
		||||
      deletedConfiguration.domain ==
 | 
			
		||||
      this._configService.get<string>('SERVICE_CONFIGURATION_DOMAIN')
 | 
			
		||||
    ) {
 | 
			
		||||
      const deleteConfigurationRequest = new DeleteConfigurationRequest();
 | 
			
		||||
      deleteConfigurationRequest.domain = deletedConfiguration.domain;
 | 
			
		||||
      deleteConfigurationRequest.key = deletedConfiguration.key;
 | 
			
		||||
      await this._commandBus.execute(
 | 
			
		||||
        new DeleteConfigurationCommand(deleteConfigurationRequest),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @RabbitSubscribe({
 | 
			
		||||
    name: 'propagateConfiguration',
 | 
			
		||||
  })
 | 
			
		||||
  public async propagateConfigurationsHandler(message: string) {
 | 
			
		||||
    const configurations: Array<Configuration> = JSON.parse(message);
 | 
			
		||||
    configurations.forEach(async (configuration) => {
 | 
			
		||||
      if (
 | 
			
		||||
        configuration.domain ==
 | 
			
		||||
        this._configService.get<string>('SERVICE_CONFIGURATION_DOMAIN')
 | 
			
		||||
      ) {
 | 
			
		||||
        const setConfigurationRequest: SetConfigurationRequest =
 | 
			
		||||
          new SetConfigurationRequest();
 | 
			
		||||
        setConfigurationRequest.domain = configuration.domain;
 | 
			
		||||
        setConfigurationRequest.key = configuration.key;
 | 
			
		||||
        setConfigurationRequest.value = configuration.value;
 | 
			
		||||
        await this._commandBus.execute(
 | 
			
		||||
          new SetConfigurationCommand(setConfigurationRequest),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import { InjectRedis } from '@liaoliaots/nestjs-redis';
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { Redis } from 'ioredis';
 | 
			
		||||
import { IConfigurationRepository } from '../../domain/interfaces/configuration.repository';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class RedisConfigurationRepository extends IConfigurationRepository {
 | 
			
		||||
  constructor(@InjectRedis() private readonly _redis: Redis) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async get(key: string): Promise<string> {
 | 
			
		||||
    return await this._redis.get(key);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async set(key: string, value: string) {
 | 
			
		||||
    await this._redis.set(key, value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async del(key: string) {
 | 
			
		||||
    await this._redis.del(key);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
import { DeleteConfigurationRequest } from '../domain/dtos/delete-configuration.request';
 | 
			
		||||
 | 
			
		||||
export class DeleteConfigurationCommand {
 | 
			
		||||
  readonly deleteConfigurationRequest: DeleteConfigurationRequest;
 | 
			
		||||
 | 
			
		||||
  constructor(deleteConfigurationRequest: DeleteConfigurationRequest) {
 | 
			
		||||
    this.deleteConfigurationRequest = deleteConfigurationRequest;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
import { SetConfigurationRequest } from '../domain/dtos/set-configuration.request';
 | 
			
		||||
 | 
			
		||||
export class SetConfigurationCommand {
 | 
			
		||||
  readonly setConfigurationRequest: SetConfigurationRequest;
 | 
			
		||||
 | 
			
		||||
  constructor(setConfigurationRequest: SetConfigurationRequest) {
 | 
			
		||||
    this.setConfigurationRequest = setConfigurationRequest;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,68 +0,0 @@
 | 
			
		|||
import { RabbitMQConfig, RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
 | 
			
		||||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { CqrsModule } from '@nestjs/cqrs';
 | 
			
		||||
import { ConfigurationMessagerController } from './adapters/primaries/configuration-messager.controller';
 | 
			
		||||
import { RedisConfigurationRepository } from './adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { DeleteConfigurationUseCase } from './domain/usecases/delete-configuration.usecase';
 | 
			
		||||
import { GetConfigurationUseCase } from './domain/usecases/get-configuration.usecase';
 | 
			
		||||
import { SetConfigurationUseCase } from './domain/usecases/set-configuration.usecase';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    CqrsModule,
 | 
			
		||||
    RedisModule.forRootAsync({
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
      useFactory: async (
 | 
			
		||||
        configService: ConfigService,
 | 
			
		||||
      ): Promise<RedisModuleOptions> => ({
 | 
			
		||||
        config: {
 | 
			
		||||
          host: configService.get<string>('REDIS_HOST'),
 | 
			
		||||
          port: configService.get<number>('REDIS_PORT'),
 | 
			
		||||
          password: configService.get<string>('REDIS_PASSWORD'),
 | 
			
		||||
        },
 | 
			
		||||
      }),
 | 
			
		||||
    }),
 | 
			
		||||
    RabbitMQModule.forRootAsync(RabbitMQModule, {
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
      useFactory: async (
 | 
			
		||||
        configService: ConfigService,
 | 
			
		||||
      ): Promise<RabbitMQConfig> => ({
 | 
			
		||||
        exchanges: [
 | 
			
		||||
          {
 | 
			
		||||
            name: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            type: 'topic',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        handlers: {
 | 
			
		||||
          setConfiguration: {
 | 
			
		||||
            exchange: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            routingKey: ['configuration.create', 'configuration.update'],
 | 
			
		||||
          },
 | 
			
		||||
          deleteConfiguration: {
 | 
			
		||||
            exchange: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            routingKey: 'configuration.delete',
 | 
			
		||||
          },
 | 
			
		||||
          propagateConfiguration: {
 | 
			
		||||
            exchange: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            routingKey: 'configuration.propagate',
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        uri: configService.get<string>('RMQ_URI'),
 | 
			
		||||
        connectionInitOptions: { wait: false },
 | 
			
		||||
        enableControllerDiscovery: true,
 | 
			
		||||
      }),
 | 
			
		||||
    }),
 | 
			
		||||
  ],
 | 
			
		||||
  controllers: [ConfigurationMessagerController],
 | 
			
		||||
  providers: [
 | 
			
		||||
    GetConfigurationUseCase,
 | 
			
		||||
    SetConfigurationUseCase,
 | 
			
		||||
    DeleteConfigurationUseCase,
 | 
			
		||||
    RedisConfigurationRepository,
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class ConfigurationModule {}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
import { IsNotEmpty, IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
export class DeleteConfigurationRequest {
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  domain: string;
 | 
			
		||||
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  key: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
import { IsNotEmpty, IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
export class SetConfigurationRequest {
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  domain: string;
 | 
			
		||||
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  key: string;
 | 
			
		||||
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  value: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import { AutoMap } from '@automapper/classes';
 | 
			
		||||
 | 
			
		||||
export class Configuration {
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  domain: string;
 | 
			
		||||
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  key: string;
 | 
			
		||||
 | 
			
		||||
  @AutoMap()
 | 
			
		||||
  value: string;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +0,0 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export abstract class IConfigurationRepository {
 | 
			
		||||
  abstract get(key: string): Promise<string>;
 | 
			
		||||
  abstract set(key: string, value: string): void;
 | 
			
		||||
  abstract del(key: string): void;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
import { CommandHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
 | 
			
		||||
 | 
			
		||||
@CommandHandler(DeleteConfigurationCommand)
 | 
			
		||||
export class DeleteConfigurationUseCase {
 | 
			
		||||
  constructor(private _configurationRepository: RedisConfigurationRepository) {}
 | 
			
		||||
 | 
			
		||||
  async execute(deleteConfigurationCommand: DeleteConfigurationCommand) {
 | 
			
		||||
    await this._configurationRepository.del(
 | 
			
		||||
      deleteConfigurationCommand.deleteConfigurationRequest.domain +
 | 
			
		||||
        ':' +
 | 
			
		||||
        deleteConfigurationCommand.deleteConfigurationRequest.key,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
import { QueryHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { GetConfigurationQuery } from '../../queries/get-configuration.query';
 | 
			
		||||
 | 
			
		||||
@QueryHandler(GetConfigurationQuery)
 | 
			
		||||
export class GetConfigurationUseCase {
 | 
			
		||||
  constructor(private _configurationRepository: RedisConfigurationRepository) {}
 | 
			
		||||
 | 
			
		||||
  async execute(getConfigurationQuery: GetConfigurationQuery): Promise<string> {
 | 
			
		||||
    return this._configurationRepository.get(
 | 
			
		||||
      getConfigurationQuery.domain + ':' + getConfigurationQuery.key,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,17 +0,0 @@
 | 
			
		|||
import { CommandHandler } from '@nestjs/cqrs';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { SetConfigurationCommand } from '../../commands/set-configuration.command';
 | 
			
		||||
 | 
			
		||||
@CommandHandler(SetConfigurationCommand)
 | 
			
		||||
export class SetConfigurationUseCase {
 | 
			
		||||
  constructor(private _configurationRepository: RedisConfigurationRepository) {}
 | 
			
		||||
 | 
			
		||||
  async execute(setConfigurationCommand: SetConfigurationCommand) {
 | 
			
		||||
    await this._configurationRepository.set(
 | 
			
		||||
      setConfigurationCommand.setConfigurationRequest.domain +
 | 
			
		||||
        ':' +
 | 
			
		||||
        setConfigurationCommand.setConfigurationRequest.key,
 | 
			
		||||
      setConfigurationCommand.setConfigurationRequest.value,
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +0,0 @@
 | 
			
		|||
export class GetConfigurationQuery {
 | 
			
		||||
  readonly domain: string;
 | 
			
		||||
  readonly key: string;
 | 
			
		||||
 | 
			
		||||
  constructor(domain: string, key: string) {
 | 
			
		||||
    this.domain = domain;
 | 
			
		||||
    this.key = key;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,49 +0,0 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { DeleteConfigurationCommand } from '../../commands/delete-configuration.command';
 | 
			
		||||
import { DeleteConfigurationRequest } from '../../domain/dtos/delete-configuration.request';
 | 
			
		||||
import { DeleteConfigurationUseCase } from '../../domain/usecases/delete-configuration.usecase';
 | 
			
		||||
 | 
			
		||||
const mockRedisConfigurationRepository = {
 | 
			
		||||
  del: jest.fn().mockResolvedValue(undefined),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('DeleteConfigurationUseCase', () => {
 | 
			
		||||
  let deleteConfigurationUseCase: DeleteConfigurationUseCase;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: RedisConfigurationRepository,
 | 
			
		||||
          useValue: mockRedisConfigurationRepository,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        DeleteConfigurationUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    deleteConfigurationUseCase = module.get<DeleteConfigurationUseCase>(
 | 
			
		||||
      DeleteConfigurationUseCase,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(deleteConfigurationUseCase).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execute', () => {
 | 
			
		||||
    it('should delete a key', async () => {
 | 
			
		||||
      jest.spyOn(mockRedisConfigurationRepository, 'del');
 | 
			
		||||
      const deleteConfigurationRequest: DeleteConfigurationRequest = {
 | 
			
		||||
        domain: 'my-domain',
 | 
			
		||||
        key: 'my-key',
 | 
			
		||||
      };
 | 
			
		||||
      await deleteConfigurationUseCase.execute(
 | 
			
		||||
        new DeleteConfigurationCommand(deleteConfigurationRequest),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(mockRedisConfigurationRepository.del).toHaveBeenCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,43 +0,0 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { GetConfigurationUseCase } from '../../domain/usecases/get-configuration.usecase';
 | 
			
		||||
import { GetConfigurationQuery } from '../../queries/get-configuration.query';
 | 
			
		||||
 | 
			
		||||
const mockRedisConfigurationRepository = {
 | 
			
		||||
  get: jest.fn().mockResolvedValue('my-value'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('GetConfigurationUseCase', () => {
 | 
			
		||||
  let getConfigurationUseCase: GetConfigurationUseCase;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: RedisConfigurationRepository,
 | 
			
		||||
          useValue: mockRedisConfigurationRepository,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        GetConfigurationUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    getConfigurationUseCase = module.get<GetConfigurationUseCase>(
 | 
			
		||||
      GetConfigurationUseCase,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(getConfigurationUseCase).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execute', () => {
 | 
			
		||||
    it('should get a value for a key', async () => {
 | 
			
		||||
      const value: string = await getConfigurationUseCase.execute(
 | 
			
		||||
        new GetConfigurationQuery('my-domain', 'my-key'),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(value).toBe('my-value');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { getRedisToken } from '@liaoliaots/nestjs-redis';
 | 
			
		||||
 | 
			
		||||
const mockRedis = {
 | 
			
		||||
  get: jest.fn().mockResolvedValue('myValue'),
 | 
			
		||||
  set: jest.fn().mockImplementation(),
 | 
			
		||||
  del: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('RedisConfigurationRepository', () => {
 | 
			
		||||
  let redisConfigurationRepository: RedisConfigurationRepository;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: getRedisToken('default'),
 | 
			
		||||
          useValue: mockRedis,
 | 
			
		||||
        },
 | 
			
		||||
        RedisConfigurationRepository,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    redisConfigurationRepository = module.get<RedisConfigurationRepository>(
 | 
			
		||||
      RedisConfigurationRepository,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(redisConfigurationRepository).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('interact', () => {
 | 
			
		||||
    it('should get a value', async () => {
 | 
			
		||||
      expect(await redisConfigurationRepository.get('myKey')).toBe('myValue');
 | 
			
		||||
    });
 | 
			
		||||
    it('should set a value', async () => {
 | 
			
		||||
      expect(
 | 
			
		||||
        await redisConfigurationRepository.set('myKey', 'myValue'),
 | 
			
		||||
      ).toBeUndefined();
 | 
			
		||||
    });
 | 
			
		||||
    it('should delete a value', async () => {
 | 
			
		||||
      expect(await redisConfigurationRepository.del('myKey')).toBeUndefined();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,50 +0,0 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { RedisConfigurationRepository } from '../../adapters/secondaries/redis-configuration.repository';
 | 
			
		||||
import { SetConfigurationCommand } from '../../commands/set-configuration.command';
 | 
			
		||||
import { SetConfigurationRequest } from '../../domain/dtos/set-configuration.request';
 | 
			
		||||
import { SetConfigurationUseCase } from '../../domain/usecases/set-configuration.usecase';
 | 
			
		||||
 | 
			
		||||
const mockRedisConfigurationRepository = {
 | 
			
		||||
  set: jest.fn().mockResolvedValue(undefined),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('SetConfigurationUseCase', () => {
 | 
			
		||||
  let setConfigurationUseCase: SetConfigurationUseCase;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        {
 | 
			
		||||
          provide: RedisConfigurationRepository,
 | 
			
		||||
          useValue: mockRedisConfigurationRepository,
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        SetConfigurationUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    setConfigurationUseCase = module.get<SetConfigurationUseCase>(
 | 
			
		||||
      SetConfigurationUseCase,
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(setConfigurationUseCase).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execute', () => {
 | 
			
		||||
    it('should set a value for a key', async () => {
 | 
			
		||||
      jest.spyOn(mockRedisConfigurationRepository, 'set');
 | 
			
		||||
      const setConfigurationRequest: SetConfigurationRequest = {
 | 
			
		||||
        domain: 'my-domain',
 | 
			
		||||
        key: 'my-key',
 | 
			
		||||
        value: 'my-value',
 | 
			
		||||
      };
 | 
			
		||||
      await setConfigurationUseCase.execute(
 | 
			
		||||
        new SetConfigurationCommand(setConfigurationRequest),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      expect(mockRedisConfigurationRepository.set).toHaveBeenCalledTimes(1);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
import { Controller } from '@nestjs/common';
 | 
			
		||||
import { GrpcMethod } from '@nestjs/microservices';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
			
		||||
 | 
			
		||||
enum ServingStatus {
 | 
			
		||||
  UNKNOWN = 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,7 @@ interface HealthCheckResponse {
 | 
			
		|||
@Controller()
 | 
			
		||||
export class HealthServerController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
			
		||||
    private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @GrpcMethod('Health', 'Check')
 | 
			
		||||
| 
						 | 
				
			
			@ -29,12 +29,12 @@ export class HealthServerController {
 | 
			
		|||
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
    metadata: any,
 | 
			
		||||
  ): Promise<HealthCheckResponse> {
 | 
			
		||||
    const healthCheck = await this._prismaHealthIndicatorUseCase.isHealthy(
 | 
			
		||||
      'prisma',
 | 
			
		||||
    const healthCheck = await this.repositoriesHealthIndicatorUseCase.isHealthy(
 | 
			
		||||
      'repositories',
 | 
			
		||||
    );
 | 
			
		||||
    return {
 | 
			
		||||
      status:
 | 
			
		||||
        healthCheck['prisma'].status == 'up'
 | 
			
		||||
        healthCheck['repositories'].status == 'up'
 | 
			
		||||
          ? ServingStatus.SERVING
 | 
			
		||||
          : ServingStatus.NOT_SERVING,
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,33 @@
 | 
			
		|||
import { Controller, Get } from '@nestjs/common';
 | 
			
		||||
import { Controller, Get, Inject } from '@nestjs/common';
 | 
			
		||||
import {
 | 
			
		||||
  HealthCheckService,
 | 
			
		||||
  HealthCheck,
 | 
			
		||||
  HealthCheckResult,
 | 
			
		||||
} from '@nestjs/terminus';
 | 
			
		||||
import { Messager } from '../secondaries/messager';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
			
		||||
import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
			
		||||
import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
			
		||||
 | 
			
		||||
@Controller('health')
 | 
			
		||||
export class HealthController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase,
 | 
			
		||||
    private _healthCheckService: HealthCheckService,
 | 
			
		||||
    private _messager: Messager,
 | 
			
		||||
    private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
 | 
			
		||||
    private healthCheckService: HealthCheckService,
 | 
			
		||||
    @Inject(MESSAGE_PUBLISHER)
 | 
			
		||||
    private readonly messagePublisher: IPublishMessage,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Get()
 | 
			
		||||
  @HealthCheck()
 | 
			
		||||
  async check() {
 | 
			
		||||
    try {
 | 
			
		||||
      return await this._healthCheckService.check([
 | 
			
		||||
        async () => this._prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
			
		||||
      return await this.healthCheckService.check([
 | 
			
		||||
        async () =>
 | 
			
		||||
          this.repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
 | 
			
		||||
      ]);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      const healthCheckResult: HealthCheckResult = error.response;
 | 
			
		||||
      this._messager.publish(
 | 
			
		||||
      this.messagePublisher.publish(
 | 
			
		||||
        'logging.user.health.crit',
 | 
			
		||||
        JSON.stringify(healthCheckResult.error),
 | 
			
		||||
      );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export abstract class IMessageBroker {
 | 
			
		||||
  exchange: string;
 | 
			
		||||
 | 
			
		||||
  constructor(exchange: string) {
 | 
			
		||||
    this.exchange = exchange;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  abstract publish(routingKey: string, message: string): void;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import { Inject, Injectable } from '@nestjs/common';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
			
		||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
			
		||||
import { IPublishMessage } from 'src/interfaces/message-publisher';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class MessagePublisher implements IPublishMessage {
 | 
			
		||||
  constructor(
 | 
			
		||||
    @Inject(MESSAGE_BROKER_PUBLISHER)
 | 
			
		||||
    private readonly messageBrokerPublisher: MessageBrokerPublisher,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  publish = (routingKey: string, message: string): void => {
 | 
			
		||||
    this.messageBrokerPublisher.publish(routingKey, message);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { IMessageBroker } from './message-broker';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class Messager extends IMessageBroker {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly _amqpConnection: AmqpConnection,
 | 
			
		||||
    configService: ConfigService,
 | 
			
		||||
  ) {
 | 
			
		||||
    super(configService.get<string>('RMQ_EXCHANGE'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  publish(routingKey: string, message: string): void {
 | 
			
		||||
    this._amqpConnection.publish(this.exchange, routingKey, message);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
export interface ICheckRepository {
 | 
			
		||||
  healthCheck(): Promise<boolean>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import {
 | 
			
		||||
  HealthCheckError,
 | 
			
		||||
  HealthIndicator,
 | 
			
		||||
  HealthIndicatorResult,
 | 
			
		||||
} from '@nestjs/terminus';
 | 
			
		||||
import { AdsRepository } from '../../../ad/adapters/secondaries/ads.repository';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class PrismaHealthIndicatorUseCase extends HealthIndicator {
 | 
			
		||||
  constructor(private readonly _repository: AdsRepository) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async isHealthy(key: string): Promise<HealthIndicatorResult> {
 | 
			
		||||
    try {
 | 
			
		||||
      await this._repository.healthCheck();
 | 
			
		||||
      return this.getStatus(key, true);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw new HealthCheckError('Prisma', {
 | 
			
		||||
        prisma: e.message,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import {
 | 
			
		||||
  HealthCheckError,
 | 
			
		||||
  HealthIndicator,
 | 
			
		||||
  HealthIndicatorResult,
 | 
			
		||||
} from '@nestjs/terminus';
 | 
			
		||||
import { ICheckRepository } from '../interfaces/check-repository.interface';
 | 
			
		||||
import { AdsRepository } from '../../../ad/adapters/secondaries/ads.repository';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class RepositoriesHealthIndicatorUseCase extends HealthIndicator {
 | 
			
		||||
  private checkRepositories: ICheckRepository[];
 | 
			
		||||
  constructor(private readonly adsRepository: AdsRepository) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.checkRepositories = [adsRepository];
 | 
			
		||||
  }
 | 
			
		||||
  isHealthy = async (key: string): Promise<HealthIndicatorResult> => {
 | 
			
		||||
    try {
 | 
			
		||||
      await Promise.all(
 | 
			
		||||
        this.checkRepositories.map(
 | 
			
		||||
          async (checkRepository: ICheckRepository) => {
 | 
			
		||||
            await checkRepository.healthCheck();
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      return this.getStatus(key, true);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      throw new HealthCheckError('Repository', {
 | 
			
		||||
        repository: e.message,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,34 +1,28 @@
 | 
			
		|||
import { Module } from '@nestjs/common';
 | 
			
		||||
import { HealthServerController } from './adapters/primaries/health-server.controller';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from './domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { AdsRepository } from '../ad/adapters/secondaries/ads.repository';
 | 
			
		||||
import { DatabaseModule } from '../database/database.module';
 | 
			
		||||
import { HealthController } from './adapters/primaries/health.controller';
 | 
			
		||||
import { TerminusModule } from '@nestjs/terminus';
 | 
			
		||||
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigModule, ConfigService } from '@nestjs/config';
 | 
			
		||||
import { Messager } from './adapters/secondaries/messager';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants';
 | 
			
		||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
 | 
			
		||||
import { MessagePublisher } from './adapters/secondaries/message-publisher';
 | 
			
		||||
import { RepositoriesHealthIndicatorUseCase } from './domain/usecases/repositories.health-indicator.usecase';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
    TerminusModule,
 | 
			
		||||
    RabbitMQModule.forRootAsync(RabbitMQModule, {
 | 
			
		||||
      imports: [ConfigModule],
 | 
			
		||||
      useFactory: async (configService: ConfigService) => ({
 | 
			
		||||
        exchanges: [
 | 
			
		||||
          {
 | 
			
		||||
            name: configService.get<string>('RMQ_EXCHANGE'),
 | 
			
		||||
            type: 'topic',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
        uri: configService.get<string>('RMQ_URI'),
 | 
			
		||||
        connectionInitOptions: { wait: false },
 | 
			
		||||
      }),
 | 
			
		||||
      inject: [ConfigService],
 | 
			
		||||
    }),
 | 
			
		||||
    DatabaseModule,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [TerminusModule, DatabaseModule],
 | 
			
		||||
  controllers: [HealthServerController, HealthController],
 | 
			
		||||
  providers: [PrismaHealthIndicatorUseCase, AdsRepository, Messager],
 | 
			
		||||
  providers: [
 | 
			
		||||
    RepositoriesHealthIndicatorUseCase,
 | 
			
		||||
    AdsRepository,
 | 
			
		||||
    {
 | 
			
		||||
      provide: MESSAGE_BROKER_PUBLISHER,
 | 
			
		||||
      useClass: MessageBrokerPublisher,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      provide: MESSAGE_PUBLISHER,
 | 
			
		||||
      useClass: MessagePublisher,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
export class HealthModule {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { MessagePublisher } from '../../adapters/secondaries/message-publisher';
 | 
			
		||||
import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants';
 | 
			
		||||
 | 
			
		||||
const mockMessageBrokerPublisher = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Messager', () => {
 | 
			
		||||
  let messagePublisher: MessagePublisher;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      imports: [],
 | 
			
		||||
      providers: [
 | 
			
		||||
        MessagePublisher,
 | 
			
		||||
        {
 | 
			
		||||
          provide: MESSAGE_BROKER_PUBLISHER,
 | 
			
		||||
          useValue: mockMessageBrokerPublisher,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    messagePublisher = module.get<MessagePublisher>(MessagePublisher);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(messagePublisher).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should publish a message', async () => {
 | 
			
		||||
    jest.spyOn(mockMessageBrokerPublisher, 'publish');
 | 
			
		||||
    messagePublisher.publish('health.info', 'my-test');
 | 
			
		||||
    expect(mockMessageBrokerPublisher.publish).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,47 +0,0 @@
 | 
			
		|||
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
 | 
			
		||||
import { ConfigService } from '@nestjs/config';
 | 
			
		||||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { Messager } from '../../adapters/secondaries/messager';
 | 
			
		||||
 | 
			
		||||
const mockAmqpConnection = {
 | 
			
		||||
  publish: jest.fn().mockImplementation(),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mockConfigService = {
 | 
			
		||||
  get: jest.fn().mockResolvedValue({
 | 
			
		||||
    RMQ_EXCHANGE: 'mobicoop',
 | 
			
		||||
  }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('Messager', () => {
 | 
			
		||||
  let messager: Messager;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      imports: [],
 | 
			
		||||
      providers: [
 | 
			
		||||
        Messager,
 | 
			
		||||
        {
 | 
			
		||||
          provide: AmqpConnection,
 | 
			
		||||
          useValue: mockAmqpConnection,
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          provide: ConfigService,
 | 
			
		||||
          useValue: mockConfigService,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    messager = module.get<Messager>(Messager);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(messager).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should publish a message', async () => {
 | 
			
		||||
    jest.spyOn(mockAmqpConnection, 'publish');
 | 
			
		||||
    messager.publish('test.create.info', 'my-test');
 | 
			
		||||
    expect(mockAmqpConnection.publish).toHaveBeenCalledTimes(1);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
import { Test, TestingModule } from '@nestjs/testing';
 | 
			
		||||
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
 | 
			
		||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
 | 
			
		||||
import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase';
 | 
			
		||||
import { AdsRepository } from '../../../ad/adapters/secondaries/ads.repository';
 | 
			
		||||
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
 | 
			
		||||
 | 
			
		||||
const mockAdsRepository = {
 | 
			
		||||
  healthCheck: jest
 | 
			
		||||
| 
						 | 
				
			
			@ -11,47 +10,45 @@ const mockAdsRepository = {
 | 
			
		|||
      return Promise.resolve(true);
 | 
			
		||||
    })
 | 
			
		||||
    .mockImplementation(() => {
 | 
			
		||||
      throw new PrismaClientKnownRequestError('Service unavailable', {
 | 
			
		||||
        code: 'code',
 | 
			
		||||
        clientVersion: 'version',
 | 
			
		||||
      });
 | 
			
		||||
      throw new Error('an error occured in the repository');
 | 
			
		||||
    }),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('PrismaHealthIndicatorUseCase', () => {
 | 
			
		||||
  let prismaHealthIndicatorUseCase: PrismaHealthIndicatorUseCase;
 | 
			
		||||
describe('RepositoriesHealthIndicatorUseCase', () => {
 | 
			
		||||
  let repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase;
 | 
			
		||||
 | 
			
		||||
  beforeAll(async () => {
 | 
			
		||||
    const module: TestingModule = await Test.createTestingModule({
 | 
			
		||||
      providers: [
 | 
			
		||||
        RepositoriesHealthIndicatorUseCase,
 | 
			
		||||
        {
 | 
			
		||||
          provide: AdsRepository,
 | 
			
		||||
          useValue: mockAdsRepository,
 | 
			
		||||
        },
 | 
			
		||||
        PrismaHealthIndicatorUseCase,
 | 
			
		||||
      ],
 | 
			
		||||
    }).compile();
 | 
			
		||||
 | 
			
		||||
    prismaHealthIndicatorUseCase = module.get<PrismaHealthIndicatorUseCase>(
 | 
			
		||||
      PrismaHealthIndicatorUseCase,
 | 
			
		||||
    );
 | 
			
		||||
    repositoriesHealthIndicatorUseCase =
 | 
			
		||||
      module.get<RepositoriesHealthIndicatorUseCase>(
 | 
			
		||||
        RepositoriesHealthIndicatorUseCase,
 | 
			
		||||
      );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should be defined', () => {
 | 
			
		||||
    expect(prismaHealthIndicatorUseCase).toBeDefined();
 | 
			
		||||
    expect(repositoriesHealthIndicatorUseCase).toBeDefined();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('execute', () => {
 | 
			
		||||
    it('should check health successfully', async () => {
 | 
			
		||||
      const healthIndicatorResult: HealthIndicatorResult =
 | 
			
		||||
        await prismaHealthIndicatorUseCase.isHealthy('prisma');
 | 
			
		||||
        await repositoriesHealthIndicatorUseCase.isHealthy('repositories');
 | 
			
		||||
 | 
			
		||||
      expect(healthIndicatorResult['prisma'].status).toBe('up');
 | 
			
		||||
      expect(healthIndicatorResult['repositories'].status).toBe('up');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should throw an error if database is unavailable', async () => {
 | 
			
		||||
      await expect(
 | 
			
		||||
        prismaHealthIndicatorUseCase.isHealthy('prisma'),
 | 
			
		||||
        repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
 | 
			
		||||
      ).rejects.toBeInstanceOf(HealthCheckError);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
		Loading…
	
		Reference in New Issue