diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnector.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnector.scala index 4b40f8392..221a281da 100644 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnector.scala +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnector.scala @@ -17,27 +17,28 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors import com.codahale.metrics.MetricRegistry +import com.fasterxml.jackson.core.JacksonException import play.api.Logging +import play.api.http.Status.INTERNAL_SERVER_ERROR +import play.api.libs.json.{JsResultException, Json, Reads} import uk.gov.hmrc.excisemovementcontrolsystemapi.config.AppConfig import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EISErrorResponseDetails +import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISSubmissionResponse.format import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis._ import uk.gov.hmrc.excisemovementcontrolsystemapi.models.messages.IEMessage +import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader +import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService.DateTimeFormat import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.{DateTimeService, EmcsUtils} +import uk.gov.hmrc.http.HttpReads.Implicits._ import uk.gov.hmrc.http.client.HttpClientV2 -import uk.gov.hmrc.http.{HeaderCarrier, HttpErrorFunctions, HttpReads, HttpResponse, StringContextOps} +import uk.gov.hmrc.http.{HeaderCarrier, HttpErrorFunctions, HttpResponse, StringContextOps} +import java.time.Instant +import java.util.UUID import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} import scala.util.control.NonFatal import scala.xml.NodeSeq -import HttpReads.Implicits._ -import play.api.http.Status.INTERNAL_SERVER_ERROR -import play.api.libs.json.{Json, Reads} -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISSubmissionResponse.format -import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService.DateTimeFormat - -import java.util.UUID class EISSubmissionConnector @Inject() ( httpClient: HttpClientV2, @@ -59,7 +60,18 @@ class EISSubmissionConnector @Inject() ( (hc.withExtraHeaders(HttpHeader.xCorrelationId -> correlationId), correlationId) } - implicit def errorRead(status: Int): Reads[EISErrorResponseDetails] = + private def internalError(timestamp: Instant, correlationId: String): Either[EISErrorResponseDetails, Nothing] = + Left( + EISErrorResponseDetails( + INTERNAL_SERVER_ERROR, + timestamp, + "Internal server error", + "Unexpected error occurred while processing Submission request", + correlationId + ) + ) + + private def errorRead(status: Int): Reads[EISErrorResponseDetails] = Json .reads[EISErrorResponse] .map(error => EISErrorResponseDetails.createFromEISError(status, dateTimeService.timestamp(), error)) @@ -94,22 +106,27 @@ class EISSubmissionConnector @Inject() ( if (is2xx(response.status)) { Right(response.json.as[EISSubmissionResponse]) } else { - Left(response.json.as[EISErrorResponseDetails](errorRead(response.status))) + val errorDetails = response.json.as[EISErrorResponseDetails](errorRead(response.status)) + logger.warn(EISErrorMessage(createdDateTime, s"status: ${errorDetails.status}", correlationId, messageType)) + Left(errorDetails) } } .andThen { case _ => timer.stop() } - .recover { case NonFatal(ex) => - logger.warn(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, messageType), ex) + .recover { + case _: JacksonException => + // JSON parsing error + logger.error(EISErrorMessage.parseError(createdDateTime, correlationId, messageType)) + internalError(timestamp, correlationId) + + case _: JsResultException => + // JSON deserialization error + logger.error(EISErrorMessage.readError(createdDateTime, correlationId, messageType)) + internalError(timestamp, correlationId) - Left( - EISErrorResponseDetails( - INTERNAL_SERVER_ERROR, - timestamp, - "Internal server error", - "Unexpected error occurred while processing Submission request", - correlationId - ) - ) + case NonFatal(ex) => + // Something else + logger.error(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, messageType), ex) + internalError(timestamp, correlationId) } } diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnector.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnector.scala index 9cff1b7cf..d3e0ed3d7 100644 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnector.scala +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnector.scala @@ -17,12 +17,13 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors import com.codahale.metrics.MetricRegistry +import com.fasterxml.jackson.core.JacksonException import play.api.Logging -import play.api.libs.json.Json +import play.api.http.Status.{BAD_REQUEST, NOT_FOUND, UNAUTHORIZED} +import play.api.libs.json.{JsResultException, Json} import play.api.mvc.Result -import play.api.mvc.Results.InternalServerError +import play.api.mvc.Results.{InternalServerError, Status} import uk.gov.hmrc.excisemovementcontrolsystemapi.config.AppConfig -import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util.{PreValidateTraderETDSHttpReader, PreValidateTraderHttpReader} import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EisErrorResponsePresentation import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis._ import uk.gov.hmrc.excisemovementcontrolsystemapi.models.preValidateTrader.request.{ExciseTraderETDSRequest, PreValidateTraderRequest} @@ -31,8 +32,10 @@ import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService.DateTimeFormat import uk.gov.hmrc.http.client.HttpClientV2 -import uk.gov.hmrc.http.{HeaderCarrier, HttpReads, StringContextOps} +import uk.gov.hmrc.http.HttpReads.Implicits._ +import uk.gov.hmrc.http.{HeaderCarrier, StringContextOps, UpstreamErrorResponse} +import java.time.Instant import java.util.UUID import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} @@ -56,6 +59,14 @@ class PreValidateTraderConnector @Inject() ( (hc.withExtraHeaders(HttpHeader.xCorrelationId -> correlationId), correlationId) } + private def internalError(timestamp: Instant, correlationId: String) = + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + correlationId + ) + def submitMessage(request: PreValidateTraderRequest, ern: String)(implicit hc: HeaderCarrier ): Future[Either[Result, PreValidateTraderEISResponse]] = { @@ -69,29 +80,44 @@ class PreValidateTraderConnector @Inject() ( val timestamp = dateTimeService.timestamp() val createdDateTime = timestamp.asStringInMilliseconds - implicit val reader: HttpReads[Either[Result, PreValidateTraderEISResponse]] = PreValidateTraderHttpReader( - correlationId = correlationId, - ern = ern, - createDateTime = createdDateTime, - dateTimeService = dateTimeService - ) httpClient .post(url"${appConfig.preValidateTraderUrl}")(hc2) .setHeader(build(correlationId, createdDateTime, appConfig.preValidateTraderBearerToken): _*) .withBody(Json.toJson(request)) - .execute[Either[Result, PreValidateTraderEISResponse]] + .execute[PreValidateTraderEISResponse] + .map(Right.apply[Result, PreValidateTraderEISResponse]) .andThen { case _ => timer.stop() } - .recover { case NonFatal(ex) => - logger.warn(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, "PreValidateTrader"), ex) - - val error = EisErrorResponsePresentation( - timestamp, - "Internal Server Error", - "Unexpected error occurred while processing PreValidateTrader request", - correlationId - ) - - Left(InternalServerError(Json.toJson(error))) + .recover { + case _: JacksonException => + // JSON parsing error + logger.error(EISErrorMessage.parseError(createdDateTime, correlationId, "PreValidateTrader")) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) + + case _: JsResultException => + // JSON deserialization error + logger.error(EISErrorMessage.readError(createdDateTime, correlationId, "PreValidateTrader")) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) + + case response: UpstreamErrorResponse => + // Upstream error + logger.warn( + EISErrorMessage(createdDateTime, s"status: ${response.statusCode}", correlationId, "PreValidateTrader") + ) + + //Not expecting EIS response bodies to have any payload here + val ourErrorResponse = EisErrorResponsePresentation( + dateTimeService.timestamp(), + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + correlationId + ) + + Left(Status(response.statusCode)(Json.toJson(ourErrorResponse))) + + case NonFatal(ex) => + // Something else + logger.error(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, "PreValidateTrader"), ex) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) } } @@ -109,30 +135,53 @@ class PreValidateTraderConnector @Inject() ( val timestamp = dateTimeService.timestamp() val createdDateTime = timestamp.asStringInMilliseconds - implicit val reader: HttpReads[Either[Result, ExciseTraderValidationETDSResponse]] = - PreValidateTraderETDSHttpReader( - correlationId = correlationId, - ern = ern, - createDateTime = createdDateTime, - dateTimeService = dateTimeService - ) httpClient .post(url"${appConfig.preValidateTraderETDSUrl}")(hc2) .setHeader(buildETDS(correlationId, createdDateTime, appConfig.preValidateTraderETDSBearerToken): _*) .withBody(Json.toJson(request)) - .execute[Either[Result, ExciseTraderValidationETDSResponse]] + .execute[ExciseTraderValidationETDSResponse] + .map(Right.apply[Result, ExciseTraderValidationETDSResponse]) .andThen { case _ => timer.stop() } - .recover { case NonFatal(ex) => - logger.warn(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, "PreValidateTrader"), ex) - - val error = EisErrorResponsePresentation( - timestamp, - "Internal Server Error", - "Unexpected error occurred while processing PreValidateTrader request", - correlationId - ) - - Left(InternalServerError(Json.toJson(error))) + .recover { + case _: JacksonException => + // JSON parsing error + logger.error(EISErrorMessage.parseError(createdDateTime, correlationId, "PreValidateTrader")) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) + + case _: JsResultException => + // JSON deserialization error + logger.error(EISErrorMessage.readError(createdDateTime, correlationId, "PreValidateTrader")) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) + + case response: UpstreamErrorResponse => + // Upstream error + logger.warn( + EISErrorMessage(createdDateTime, s"status: ${response.statusCode}", correlationId, "PreValidateTrader") + ) + + val ourErrorResponse = response.statusCode match { + case BAD_REQUEST | NOT_FOUND | UNAUTHORIZED => + EisErrorResponsePresentation( + dateTimeService.timestamp(), + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + correlationId + ) + case _ => + EisErrorResponsePresentation( + dateTimeService.timestamp(), + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + correlationId + ) + } + + Left(Status(response.statusCode)(Json.toJson(ourErrorResponse))) + + case NonFatal(ex) => + // Something else + logger.error(EISErrorMessage(createdDateTime, ex.getMessage, correlationId, "PreValidateTrader"), ex) + Left(InternalServerError(Json.toJson(internalError(timestamp, correlationId)))) } } diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnector.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnector.scala index 44513d28f..ac373d889 100644 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnector.scala +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnector.scala @@ -16,21 +16,23 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors +import com.fasterxml.jackson.core.JacksonException import play.api.Logging import play.api.http.Status.INTERNAL_SERVER_ERROR import play.api.http.{HeaderNames, MimeTypes} -import play.api.libs.json.Json +import play.api.libs.json.{JsResultException, Json} import play.api.mvc.Result import play.api.mvc.Results.{BadRequest, InternalServerError, NotFound} import uk.gov.hmrc.excisemovementcontrolsystemapi.config.AppConfig import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util.ResponseHandler +import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISErrorMessage import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification.NotificationResponse._ import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification._ import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService import uk.gov.hmrc.http.HttpReads.Implicits._ import uk.gov.hmrc.http.client.HttpClientV2 -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, StringContextOps} +import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, StringContextOps, UpstreamErrorResponse} import java.util.UUID import javax.inject.Inject @@ -54,6 +56,12 @@ class PushNotificationConnector @Inject() ( hc.withExtraHeaders(HttpHeader.xCorrelationId -> correlationId) } + private def internalError(boxId: String, notification: Notification) = + FailedPushNotification( + INTERNAL_SERVER_ERROR, + s"An exception occurred when sending a notification with excise number: ${notification.ern}, boxId: $boxId, messageId: ${notification.messageId}" + ) + def getDefaultBoxId( clientId: String )(implicit hc: HeaderCarrier): Future[Either[Result, SuccessBoxNotificationResponse]] = { @@ -94,33 +102,42 @@ class PushNotificationConnector @Inject() ( .post(url"$url")(hc2) .withBody(Json.toJson(notification)) .setHeader(HeaderNames.CONTENT_TYPE -> MimeTypes.JSON) - .execute[HttpResponse] + .execute[SuccessPushNotificationResponse] .map { response => - extractIfSuccessful[SuccessPushNotificationResponse](response) - .fold( - error => { - logger.error( - s"[PushNotificationConnector] - error sending notification with boxId: $boxId, status: ${response.status}, message: ${response.body}" - ) - FailedPushNotification(error.status, error.body) - }, - success => { - logger.info( - s"[PushNotificationConnector] - notification successfully sent to boxId: $boxId, for messageId: ${notification.messageId}" - ) - success - } - ) - } - .recover { case NonFatal(ex) => - logger.error( - s"[PushNotificationConnector] - error sending notification with boxId: $boxId error, for messageId: ${notification.messageId}", - ex - ) - FailedPushNotification( - INTERNAL_SERVER_ERROR, - s"An exception occurred when sending a notification with excise number: ${notification.ern}, boxId: $boxId, messageId: ${notification.messageId}" + logger.info( + s"[PushNotificationConnector] - notification successfully sent to boxId: $boxId, for messageId: ${notification.messageId}" ) + response + } + .recover { + case _: JacksonException => + // JSON parsing error + logger.error( + s"[PushNotificationConnector] - error parsing response for notification with boxId: $boxId, for messageId: ${notification.messageId}" + ) + internalError(boxId, notification) + + case _: JsResultException => + // JSON deserialization error + logger.error( + s"[PushNotificationConnector] - error deserializing response for notification with boxId: $boxId, for messageId: ${notification.messageId}" + ) + internalError(boxId, notification) + + case response: UpstreamErrorResponse => + // Upstream error + logger.error( + s"[PushNotificationConnector] - error sending notification with boxId: $boxId, status: ${response.statusCode}" + ) + FailedPushNotification(response.statusCode, response.message) + + case NonFatal(ex) => + // Something else + logger.error( + s"[PushNotificationConnector] - error sending notification with boxId: $boxId error, for messageId: ${notification.messageId}", + ex + ) + internalError(boxId, notification) } } diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReader.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReader.scala deleted file mode 100644 index e31c58fa2..000000000 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReader.scala +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2023 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import play.api.Logging -import play.api.libs.json.{Json, Reads} -import play.api.mvc.Result -import play.api.mvc.Results.{InternalServerError, Status} -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.{EISErrorMessage, EISErrorResponse, EISSubmissionResponse, RimValidationErrorResponse} -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.{EisErrorResponsePresentation, ValidationResponse} -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.http.{HttpReads, HttpResponse} - -import scala.reflect.runtime.universe.TypeTag -import scala.util.{Success, Try} - -class EISHttpReader( - val correlationId: String, - val ern: String, - val createdDateTime: String, - val dateTimeService: DateTimeService, - val messageType: String -) extends HttpReads[Either[Result, EISSubmissionResponse]] - with Logging - with ResponseHandler { - - override def read(method: String, url: String, response: HttpResponse): Either[Result, EISSubmissionResponse] = { - val result = extractIfSuccessful[EISSubmissionResponse](response) - result match { - case Right(eisResponse) => Right(eisResponse) - case Left(httpResponse: HttpResponse) => Left(handleErrorResponse(httpResponse)) - } - } - - private def handleErrorResponse( - response: HttpResponse - ): Result = { - - logger.warn(EISErrorMessage(createdDateTime, response.body, correlationId, messageType)) - - (tryAsJson[RimValidationErrorResponse](response), tryAsJson[EISErrorResponse](response)) match { - case (Some(x), None) => handleRimValidationResponse(response, x) - case (None, Some(y)) => handleEISErrorResponse(response, y) - case _ => - InternalServerError( - Json.toJson( - EisErrorResponsePresentation( - dateTimeService.timestamp(), - "Unexpected error", - "Error occurred while reading downstream response", - correlationId - ) - ) - ) - } - - } - - private def handleRimValidationResponse(response: HttpResponse, rimError: RimValidationErrorResponse): Result = { - - val validationResponse = rimError.validatorResults.map(x => - ValidationResponse( - x.errorCategory, - x.errorType, - x.errorReason, - removeControlDocumentReferences(x.errorLocation), - x.originalAttributeValue - ) - ) - - Status(response.status)( - Json.toJson( - EisErrorResponsePresentation( - dateTimeService.timestamp(), - "Validation error", - rimError.message.mkString("\n"), - rimError.emcsCorrelationId, - Some(validationResponse) - ) - ) - ) - - } - - private def handleEISErrorResponse(response: HttpResponse, eisError: EISErrorResponse) = - Status(response.status)(Json.toJson(eisError.asPresentation)) - - private def tryAsJson[A](response: HttpResponse)(implicit reads: Reads[A], tt: TypeTag[A]): Option[A] = - Try(jsonAs[A](response.body)) match { - case Success(value) => Some(value) - case _ => None - } - -} - -object EISHttpReader { - def apply( - correlationId: String, - ern: String, - createDateTime: String, - dateTimeService: DateTimeService, - messageType: String - ): EISHttpReader = - new EISHttpReader( - correlationId: String, - ern: String, - createDateTime: String, - dateTimeService: DateTimeService, - messageType: String - ) -} diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReader.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReader.scala deleted file mode 100644 index 5250087f8..000000000 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReader.scala +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2025 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import play.api.Logging -import play.api.http.Status.{BAD_REQUEST, NOT_FOUND, UNAUTHORIZED} -import play.api.libs.json.Json -import play.api.mvc.Result -import play.api.mvc.Results.Status -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EisErrorResponsePresentation -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISErrorMessage -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.preValidateTrader.response.ExciseTraderValidationETDSResponse -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.http.HttpErrorFunctions.is2xx -import uk.gov.hmrc.http.{HttpReads, HttpResponse} - -class PreValidateTraderETDSHttpReader( - val correlationId: String, - val ern: String, - val createdDateTime: String, - val dateTimeService: DateTimeService -) extends HttpReads[Either[Result, ExciseTraderValidationETDSResponse]] - with Logging - with ResponseHandler { - - override def read( - method: String, - url: String, - response: HttpResponse - ): Either[Result, ExciseTraderValidationETDSResponse] = { - - val result = extractIfSuccessful(response) - result match { - case Right(eisResponse) => - Right(eisResponse) - - case Left(httpResponse: HttpResponse) => - Left(handleErrorResponse(httpResponse)) - } - } - - private def handleErrorResponse( - response: HttpResponse - ): Result = { - - logger.warn( - EISErrorMessage( - createdDateTime, - s"status: ${response.status}, body: ${response.body}", - correlationId, - "PreValidateTrader" - ) - ) - - val ourErrorResponse = response.status match { - case BAD_REQUEST | NOT_FOUND | UNAUTHORIZED => - EisErrorResponsePresentation( - dateTimeService.timestamp(), - "PreValidateTrader error", - "Error occurred during PreValidateTrader request", - correlationId - ) - case _ => - EisErrorResponsePresentation( - dateTimeService.timestamp(), - "Internal Server Error", - "Unexpected error occurred while processing PreValidateTrader request", - correlationId - ) - } - - Status(response.status)(Json.toJson(ourErrorResponse)) - } - - def extractIfSuccessful(response: HttpResponse): Either[HttpResponse, ExciseTraderValidationETDSResponse] = - if (is2xx(response.status)) Right(extractResponse(response)) - else Left(response) - - private def extractResponse(httpResponse: HttpResponse): ExciseTraderValidationETDSResponse = - jsonAs[ExciseTraderValidationETDSResponse](httpResponse.body) -} - -object PreValidateTraderETDSHttpReader { - def apply( - correlationId: String, - ern: String, - createDateTime: String, - dateTimeService: DateTimeService - ): PreValidateTraderETDSHttpReader = - new PreValidateTraderETDSHttpReader( - correlationId, - ern, - createDateTime, - dateTimeService - ) -} diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReader.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReader.scala deleted file mode 100644 index fa07690dd..000000000 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReader.scala +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2023 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import play.api.Logging -import play.api.libs.json.Json -import play.api.mvc.Result -import play.api.mvc.Results.Status -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EisErrorResponsePresentation -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.EISErrorMessage -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.preValidateTrader.response.PreValidateTraderEISResponse -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.http.HttpErrorFunctions.is2xx -import uk.gov.hmrc.http.{HttpReads, HttpResponse} - -class PreValidateTraderHttpReader( - val correlationId: String, - val ern: String, - val createdDateTime: String, - val dateTimeService: DateTimeService -) extends HttpReads[Either[Result, PreValidateTraderEISResponse]] - with Logging - with ResponseHandler { - - override def read( - method: String, - url: String, - response: HttpResponse - ): Either[Result, PreValidateTraderEISResponse] = { - - val result = extractIfSuccessful(response) - result match { - case Right(eisResponse) => Right(eisResponse) - case Left(httpResponse: HttpResponse) => Left(handleErrorResponse(httpResponse)) - } - } - - def extractIfSuccessful(response: HttpResponse): Either[HttpResponse, PreValidateTraderEISResponse] = - if (is2xx(response.status)) Right(extractResponse(response)) - else Left(response) - - private def extractResponse(httpResponse: HttpResponse): PreValidateTraderEISResponse = - jsonAs[PreValidateTraderEISResponse](httpResponse.body) - - private def handleErrorResponse( - response: HttpResponse - ): Result = { - - logger.warn( - EISErrorMessage( - createdDateTime, - s"status: ${response.status}, body: ${response.body}", - correlationId, - "PreValidateTrader" - ) - ) - - //Not expecting EIS response bodies to have any payload here - val ourErrorResponse = EisErrorResponsePresentation( - dateTimeService.timestamp(), - "PreValidateTrader error", - "Error occurred during PreValidateTrader request", - correlationId - ) - - Status(response.status)(Json.toJson(ourErrorResponse)) - } - -} - -object PreValidateTraderHttpReader { - def apply( - correlationId: String, - ern: String, - createDateTime: String, - dateTimeService: DateTimeService - ): PreValidateTraderHttpReader = - new PreValidateTraderHttpReader( - correlationId, - ern, - createDateTime, - dateTimeService - ) -} diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/models/eis/EISErrorMessage.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/models/eis/EISErrorMessage.scala index 100100a12..5a98cebf9 100644 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/models/eis/EISErrorMessage.scala +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/models/eis/EISErrorMessage.scala @@ -17,6 +17,31 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis object EISErrorMessage { + private def formatError( + header: String, + createDateTime: String, + correlationId: String, + messageTypes: String + ): String = + s"""$header + | messageId: $correlationId, + | correlationId: $correlationId, + | messageType: $messageTypes, + | timestamp: $createDateTime""".stripMargin + + def parseError( + createDateTime: String, + correlationId: String, + messageTypes: String + ): String = + formatError("Error parsing response JSON:", createDateTime, correlationId, messageTypes) + + def readError( + createDateTime: String, + correlationId: String, + messageTypes: String + ): String = + formatError("Error deserializing response JSON:", createDateTime, correlationId, messageTypes) def apply( createDateTime: String, @@ -24,9 +49,5 @@ object EISErrorMessage { correlationId: String, messageTypes: String ): String = - s"""EIS error with message: $message, - | messageId: $correlationId, - | correlationId: $correlationId, - | messageType: $messageTypes, - | timestamp: $createDateTime""".stripMargin + formatError(s"EIS error with message: $message,", createDateTime, correlationId, messageTypes) } diff --git a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/services/PushNotificationService.scala b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/services/PushNotificationService.scala index 13bee7c68..aa03af496 100644 --- a/app/uk/gov/hmrc/excisemovementcontrolsystemapi/services/PushNotificationService.scala +++ b/app/uk/gov/hmrc/excisemovementcontrolsystemapi/services/PushNotificationService.scala @@ -24,7 +24,6 @@ import play.api.libs.json.Json import play.api.mvc.Result import play.api.mvc.Results.BadRequest import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.PushNotificationConnector -import uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util.ResponseHandler import uk.gov.hmrc.excisemovementcontrolsystemapi.controllers.routes import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification.Notification import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification.NotificationResponse._ @@ -41,7 +40,6 @@ class PushNotificationServiceImpl @Inject() ( dateTimeService: DateTimeService )(implicit val ec: ExecutionContext) extends PushNotificationService - with ResponseHandler with Logging { def getBoxId( diff --git a/build.sbt b/build.sbt index 4c1f4d6bb..5c607b866 100755 --- a/build.sbt +++ b/build.sbt @@ -9,41 +9,38 @@ lazy val generatedV1 = (project in file("generated-v1")) .enablePlugins(ScalaxbPlugin) .settings( scalaVersion := "2.13.16", + // Suppress compiler warnings in generated code + scalacOptions += "-Wconf:src=src_managed/.*:s", libraryDependencies ++= Seq( "org.scala-lang.modules" %% "scala-xml" % "2.2.0", "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", "javax.xml.bind" % "jaxb-api" % "2.3.1", - - ), - Compile / scalaxb / scalaxbXsdSource := new File("app/xsd/v1"), + Compile / scalaxb / scalaxbXsdSource := file("app/xsd/v1"), Compile / scalaxb / scalaxbDispatchVersion := "1.1.3", Compile / scalaxb / scalaxbGenerateRuntime := true, Compile / scalaxb / scalaxbPackageName := "generated.v1", - - ) lazy val generatedV2 = (project in file("generated-v2")) .enablePlugins(ScalaxbPlugin) - .dependsOn(generatedV1) .settings( scalaVersion := "2.13.16", + // Suppress compiler warnings in generated code + scalacOptions += "-Wconf:src=src_managed/.*:s", libraryDependencies ++= Seq( "org.scala-lang.modules" %% "scala-xml" % "2.2.0", "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2", "javax.xml.bind" % "jaxb-api" % "2.3.1", - - ), - Compile / scalaxb / scalaxbXsdSource := new File("app/xsd/v2"), + Compile / scalaxb / scalaxbXsdSource := file("app/xsd/v2"), Compile / scalaxb / scalaxbDispatchVersion := "1.1.3", Compile / scalaxb / scalaxbGenerateRuntime := true, Compile / scalaxb / scalaxbPackageName := "generated.v2", ) lazy val microservice = Project("excise-movement-control-system-api", file(".")) - .dependsOn(generatedV1,generatedV2) + .dependsOn(generatedV1, generatedV2) .enablePlugins(play.sbt.PlayScala, SbtDistributablesPlugin) .settings( PlayKeys.playDefaultPort := 10250, diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnectorSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnectorSpec.scala index 754198012..864e24824 100644 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnectorSpec.scala +++ b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/EISSubmissionConnectorSpec.scala @@ -17,6 +17,7 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors import com.codahale.metrics.{MetricRegistry, Timer} +import com.fasterxml.jackson.core.JsonParseException import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchersSugar.eqTo import org.mockito.Mockito.RETURNS_DEEP_STUBS @@ -176,6 +177,40 @@ class EISSubmissionConnectorSpec ) } + "return EISErrorResponseDetails if JSON parsing fails" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockResponse.json).thenThrow(new JsonParseException("Invalid JSON object")) + + val result = await(submitExciseMovementForIE815) + + result.left.value mustBe EISErrorResponseDetails( + INTERNAL_SERVER_ERROR, + timestamp, + "Internal server error", + "Unexpected error occurred while processing Submission request", + emcsCorrelationId + ) + } + + "return EISErrorResponseDetails if JSON deserialization fails" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockResponse.json).thenReturn(Json.obj()) + + val result = await(submitExciseMovementForIE815) + + result.left.value mustBe EISErrorResponseDetails( + INTERNAL_SERVER_ERROR, + timestamp, + "Internal server error", + "Unexpected error occurred while processing Submission request", + emcsCorrelationId + ) + } + //Can probably refactor a number of these to a single iterative test "return Service Unavailable error" in { diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorETDSSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorETDSSpec.scala index 7ff762283..0219a704a 100644 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorETDSSpec.scala +++ b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorETDSSpec.scala @@ -17,6 +17,7 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors import com.codahale.metrics.{MetricRegistry, Timer} +import com.fasterxml.jackson.core.JsonParseException import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchersSugar.eqTo import org.mockito.Mockito.RETURNS_DEEP_STUBS @@ -25,7 +26,7 @@ import org.scalatest.{BeforeAndAfterEach, EitherValues} import org.scalatestplus.mockito.MockitoSugar.mock import org.scalatestplus.play.PlaySpec import play.api.http.Status._ -import play.api.libs.json.Json +import play.api.libs.json.{JsResultException, Json} import play.api.mvc.Result import play.api.mvc.Results.{BadRequest, InternalServerError, NotFound, ServiceUnavailable} import play.api.test.FakeRequest @@ -38,7 +39,7 @@ import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.TestUtils._ import uk.gov.hmrc.http.client.{HttpClientV2, RequestBuilder} -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse} +import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, UpstreamErrorResponse} import uk.gov.hmrc.play.http.HeaderCarrierConverter import java.time.Instant @@ -82,8 +83,8 @@ class PreValidateTraderConnectorETDSSpec when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[Either[Result, ExciseTraderValidationETDSResponse]](any(), any())) - .thenReturn(Future.successful(Right(validResponse))) + when(mockRequestBuilder.execute[ExciseTraderValidationETDSResponse](any(), any())) + .thenReturn(Future.successful(validResponse)) when(dateTimeService.timestamp()).thenReturn(timestamp) when(appConfig.preValidateTraderETDSUrl).thenReturn("http://localhost:8080/eis/path") @@ -102,8 +103,8 @@ class PreValidateTraderConnectorETDSSpec when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[Either[Result, ExciseTraderValidationETDSResponse]](any(), any())) - .thenReturn(Future.successful(Right(businessError))) + when(mockRequestBuilder.execute[ExciseTraderValidationETDSResponse](any(), any())) + .thenReturn(Future.successful(businessError)) val result = await(submitPreValidateTrader()) @@ -121,11 +122,20 @@ class PreValidateTraderConnectorETDSSpec when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(BadRequest("any error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", BAD_REQUEST))) val result = await(submitPreValidateTrader()) - result.left.value mustBe BadRequest("any error") + result.left.value mustBe BadRequest( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return 500 if post request fail" in { @@ -149,16 +159,67 @@ class PreValidateTraderConnectorETDSSpec ) } + "return 500 if the response JSON cannot be parsed" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[Either[Result, ExciseTraderValidationETDSResponse]](any(), any())) + .thenReturn(Future.failed(new JsonParseException("Invalid JSON"))) + + val result = await(submitPreValidateTrader()) + + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) + } + + "return 500 if the response JSON cannot be deserialized" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[Either[Result, ExciseTraderValidationETDSResponse]](any(), any())) + .thenReturn(Future.failed(JsResultException(Seq.empty))) + + val result = await(submitPreValidateTrader()) + + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) + } + "return Not found error" in { when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(NotFound("error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("error", 404))) val result = await(submitPreValidateTrader()) - result.left.value mustBe NotFound("error") + result.left.value mustBe NotFound( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return service unavailable error" in { @@ -166,11 +227,20 @@ class PreValidateTraderConnectorETDSSpec when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(ServiceUnavailable("any error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", SERVICE_UNAVAILABLE))) val result = await(submitPreValidateTrader()) - result.left.value mustBe ServiceUnavailable("any error") + result.left.value mustBe ServiceUnavailable( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return Internal service error error" in { @@ -178,11 +248,20 @@ class PreValidateTraderConnectorETDSSpec when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(InternalServerError("any error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", INTERNAL_SERVER_ERROR))) val result = await(submitPreValidateTrader()) - result.left.value mustBe InternalServerError("any error") + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "start and stop metrics" in { diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorSpec.scala index 3eb40963a..db827acfd 100644 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorSpec.scala +++ b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PreValidateTraderConnectorSpec.scala @@ -17,6 +17,7 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors import com.codahale.metrics.{MetricRegistry, Timer} +import com.fasterxml.jackson.core.JsonParseException import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchersSugar.eqTo import org.mockito.Mockito.RETURNS_DEEP_STUBS @@ -25,7 +26,7 @@ import org.scalatest.{BeforeAndAfterEach, EitherValues} import org.scalatestplus.mockito.MockitoSugar.mock import org.scalatestplus.play.PlaySpec import play.api.http.Status._ -import play.api.libs.json.Json +import play.api.libs.json.{JsResultException, Json} import play.api.mvc.Result import play.api.mvc.Results.{BadRequest, InternalServerError, NotFound, ServiceUnavailable} import play.api.test.FakeRequest @@ -38,7 +39,7 @@ import uk.gov.hmrc.excisemovementcontrolsystemapi.services.HttpHeader import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.TestUtils._ import uk.gov.hmrc.http.client.{HttpClientV2, RequestBuilder} -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse} +import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, UpstreamErrorResponse} import uk.gov.hmrc.play.http.HeaderCarrierConverter import java.time.Instant @@ -52,7 +53,7 @@ class PreValidateTraderConnectorSpec private val emcsCorrelationId = "1234566" - val request = FakeRequest().withHeaders(HttpHeader.xCorrelationId -> emcsCorrelationId) + private val request = FakeRequest().withHeaders(HttpHeader.xCorrelationId -> emcsCorrelationId) implicit val hc: HeaderCarrier = HeaderCarrierConverter.fromRequest(request) protected implicit val ec: ExecutionContext = ExecutionContext.global @@ -70,8 +71,8 @@ class PreValidateTraderConnectorSpec private val mockRequestBuilder = mock[RequestBuilder] private val validRequest = getPreValidateTraderRequest - private val validResponse = getPreValidateTraderSuccessResponse - private val businessError = getPreValidateTraderErrorResponse + private val validResponse = getPreValidateTraderSuccessEISResponse + private val businessError = getPreValidateTraderErrorEISResponse private val timestamp = Instant.parse("2023-09-17T09:32:50Z") @@ -82,8 +83,8 @@ class PreValidateTraderConnectorSpec when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Right(validResponse))) + when(mockRequestBuilder.execute[PreValidateTraderEISResponse](any(), any())) + .thenReturn(Future.successful(validResponse)) when(dateTimeService.timestamp()).thenReturn(timestamp) when(appConfig.preValidateTraderUrl).thenReturn("http://localhost:8080/eis/path") @@ -102,8 +103,8 @@ class PreValidateTraderConnectorSpec when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Right(businessError))) + when(mockRequestBuilder.execute[PreValidateTraderEISResponse](any(), any())) + .thenReturn(Future.successful(businessError)) val result = await(submitPreValidateTrader()) @@ -121,11 +122,20 @@ class PreValidateTraderConnectorSpec when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(BadRequest("any error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", BAD_REQUEST))) val result = await(submitPreValidateTrader()) - result.left.value mustBe BadRequest("any error") + result.left.value mustBe BadRequest( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return 500 if post request fail" in { @@ -149,16 +159,67 @@ class PreValidateTraderConnectorSpec ) } + "return 500 if the response JSON cannot be parsed" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) + .thenReturn(Future.failed(new JsonParseException("Invalid JSON"))) + + val result = await(submitPreValidateTrader()) + + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) + } + + "return 500 if the response JSON cannot be deserialized" in { + when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) + .thenReturn(Future.failed(JsResultException(Seq.empty))) + + val result = await(submitPreValidateTrader()) + + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "Internal Server Error", + "Unexpected error occurred while processing PreValidateTrader request", + emcsCorrelationId + ) + ) + ) + } + "return Not found error" in { when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(NotFound("error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("error", NOT_FOUND))) val result = await(submitPreValidateTrader()) - result.left.value mustBe NotFound("error") + result.left.value mustBe NotFound( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return service unavailable error" in { @@ -166,23 +227,41 @@ class PreValidateTraderConnectorSpec when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(ServiceUnavailable("any error")))) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", SERVICE_UNAVAILABLE))) val result = await(submitPreValidateTrader()) - result.left.value mustBe ServiceUnavailable("any error") + result.left.value mustBe ServiceUnavailable( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "return Internal service error error" in { when(mockHttpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[Either[Result, PreValidateTraderMessageResponse]](any(), any())) - .thenReturn(Future.successful(Left(InternalServerError("any error")))) + when(mockRequestBuilder.execute[PreValidateTraderMessageResponse](any(), any())) + .thenReturn(Future.failed(UpstreamErrorResponse("any error", INTERNAL_SERVER_ERROR))) val result = await(submitPreValidateTrader()) - result.left.value mustBe InternalServerError("any error") + result.left.value mustBe InternalServerError( + Json.toJson( + EisErrorResponsePresentation( + timestamp, + "PreValidateTrader error", + "Error occurred during PreValidateTrader request", + emcsCorrelationId + ) + ) + ) } "start and stop metrics" in { diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnectorSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnectorSpec.scala index 290fecb9f..8e9caf6a3 100644 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnectorSpec.scala +++ b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/PushNotificationConnectorSpec.scala @@ -16,6 +16,7 @@ package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors +import com.fasterxml.jackson.core.JsonParseException import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchersSugar.eqTo import org.mockito.MockitoSugar.{reset, times, verify, when} @@ -23,7 +24,7 @@ import org.scalatest.{BeforeAndAfterEach, EitherValues} import org.scalatestplus.mockito.MockitoSugar.mock import org.scalatestplus.play.PlaySpec import play.api.http.Status.{BAD_REQUEST, INTERNAL_SERVER_ERROR} -import play.api.libs.json.{JsValue, Json} +import play.api.libs.json.{JsResultException, JsValue, Json} import play.api.mvc.Results.{BadRequest, InternalServerError, NotFound} import play.api.test.Helpers.{await, defaultAwaitTimeout} import uk.gov.hmrc.excisemovementcontrolsystemapi.config.AppConfig @@ -31,7 +32,7 @@ import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification.Notificati import uk.gov.hmrc.excisemovementcontrolsystemapi.models.notification.NotificationResponse._ import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService import uk.gov.hmrc.http.client.{HttpClientV2, RequestBuilder} -import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, StringContextOps} +import uk.gov.hmrc.http.{HeaderCarrier, HttpResponse, StringContextOps, UpstreamErrorResponse} import java.time.Instant import scala.concurrent.{ExecutionContext, Future} @@ -168,8 +169,8 @@ class PushNotificationConnectorSpec extends PlaySpec with EitherValues with Befo when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[HttpResponse](any(), any())) - .thenReturn(Future.successful(HttpResponse(201, """{"notificationId": "123"}"""))) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.successful(SuccessPushNotificationResponse("123"))) val result = await(sut.postNotification(boxId, notification)) result mustBe SuccessPushNotificationResponse("123") @@ -179,8 +180,8 @@ class PushNotificationConnectorSpec extends PlaySpec with EitherValues with Befo when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[HttpResponse](any(), any())) - .thenReturn(Future.successful(HttpResponse(201, """{"notificationId": "123"}"""))) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.successful(SuccessPushNotificationResponse("123"))) await(sut.postNotification(boxId, notification)) @@ -189,12 +190,12 @@ class PushNotificationConnectorSpec extends PlaySpec with EitherValues with Befo "return an error" when { - "the notification API return an error" in { + "the notification API returns an error" in { when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[HttpResponse](any(), any())) - .thenReturn(Future.successful(HttpResponse(BAD_REQUEST, "Box ID is not a UUID"))) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.failed(UpstreamErrorResponse("Box ID is not a UUID", BAD_REQUEST))) val result = await(sut.postNotification(boxId, notification)) @@ -202,12 +203,44 @@ class PushNotificationConnectorSpec extends PlaySpec with EitherValues with Befo buildPushNotificationJsonError(BAD_REQUEST, "Box ID is not a UUID") } - "invalid json" in { + "unable to parse the response json" in { when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) - when(mockRequestBuilder.execute[HttpResponse](any(), any())) - .thenReturn(Future.successful(HttpResponse(200, "invalid json"))) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.failed(new JsonParseException("Invalid JSON"))) + + val result = await(sut.postNotification(boxId, notification)) + + Json.toJson(result.asInstanceOf[FailedPushNotification]) mustBe + buildPushNotificationJsonError( + INTERNAL_SERVER_ERROR, + s"An exception occurred when sending a notification with excise number: $ern, boxId: $boxId, messageId: $messageId" + ) + } + + "unable to deserialize the json" in { + when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.failed(JsResultException(Seq.empty))) + + val result = await(sut.postNotification(boxId, notification)) + + Json.toJson(result.asInstanceOf[FailedPushNotification]) mustBe + buildPushNotificationJsonError( + INTERNAL_SERVER_ERROR, + s"An exception occurred when sending a notification with excise number: $ern, boxId: $boxId, messageId: $messageId" + ) + } + + "some other exception occurs" in { + when(httpClient.post(any)(any)).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.setHeader(any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.withBody(any())(any(), any(), any())).thenReturn(mockRequestBuilder) + when(mockRequestBuilder.execute[SuccessPushNotificationResponse](any(), any())) + .thenReturn(Future.failed(new RuntimeException("whoops"))) val result = await(sut.postNotification(boxId, notification)) diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReaderSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReaderSpec.scala deleted file mode 100644 index 24532e825..000000000 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/EISHttpReaderSpec.scala +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2023 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import org.mockito.MockitoSugar.when -import org.scalatest.EitherValues -import org.scalatest.Inspectors.forAll -import org.scalatestplus.mockito.MockitoSugar.mock -import org.scalatestplus.play.PlaySpec -import play.api.http.Status._ -import play.api.libs.json.Json -import play.api.mvc.Results.{BadRequest, InternalServerError, NotFound, ServiceUnavailable, UnprocessableEntity} -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.eis.{EISErrorResponse, EISSubmissionResponse, RimValidationErrorResponse, RimValidatorResults} -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.{EisErrorResponsePresentation, MessageTypes, ValidationResponse} -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.http.HttpResponse - -import java.time.Instant -import scala.reflect.runtime.universe.typeOf - -class EISHttpReaderSpec extends PlaySpec with EitherValues { - - private val localDateTime = Instant.parse("2023-09-19T15:57:23.654123456Z") - private val dateTimeService = mock[DateTimeService] - when(dateTimeService.timestamp()).thenReturn(localDateTime) - - private val eisHttpParser = EISHttpReader("123", "GB123", "date time", dateTimeService, MessageTypes.IE815.value) - private val exampleEISError = Json.toJson( - EISErrorResponse( - localDateTime, - "BAD_REQUEST", - "Error", - "Error details", - "123" - ) - ) - private val exampleResponseError = Json.toJson( - EisErrorResponsePresentation(localDateTime, "Error", "Error details", "123") - ) - - "read" should { - "return EISResponse" in { - val eisResponse = EISSubmissionResponse("ok", "Success", "123") - - val result = eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(200, Json.toJson(eisResponse).toString()) - ) - - result mustBe Right(eisResponse) - } - - forAll( - Seq( - (BAD_REQUEST, BadRequest(exampleResponseError)), - (NOT_FOUND, NotFound(exampleResponseError)), - (INTERNAL_SERVER_ERROR, InternalServerError(exampleResponseError)), - (SERVICE_UNAVAILABLE, ServiceUnavailable(exampleResponseError)), - (UNPROCESSABLE_ENTITY, UnprocessableEntity(exampleResponseError)) - ) - ) { case (statusCode, expectedResult) => - s"return $statusCode" when { - s"$statusCode has returned from HttpResponse" in { - val result = eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(statusCode, exampleEISError.toString()) - ) - result.left.value mustBe expectedResult - } - } - } - - "cleanup references to the control document in from EIS validation" when { - "return a BAD_REQUEST" in { - val result = eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(BAD_REQUEST, Json.toJson(createRimValidationResponse).toString()) - ) - - result.left.value mustBe BadRequest(Json.toJson(expectedRimValidationResponse)) - } - - "return a UNPROCESSABLE_ENTITY" in { - val result = eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(UNPROCESSABLE_ENTITY, Json.toJson(createRimValidationResponse).toString()) - ) - - result.left.value mustBe UnprocessableEntity(Json.toJson(expectedRimValidationResponse)) - } - } - - "throw if cannot parse json" in { - - the[RuntimeException] thrownBy { - eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(200, """{"test":"test"}""") - ) - } must have message s"Response body could not be read as type ${typeOf[EISSubmissionResponse]}" - } - - "return a 500 Internal Server Error if EIS has supplied an unexpected error format" in { - - val eisError = Json.parse("{\"status\": 500, \"message\": \"Unexpected error\"}") - - val result = eisHttpParser.read( - "ANY", - "/foo", - HttpResponse(BAD_GATEWAY, eisError.toString()) - ) - - val error = Json.parse(s""" - |{ - | "dateTime":"2023-09-19T15:57:23.654Z", - | "message":"Unexpected error", - | "debugMessage":"Error occurred while reading downstream response", - | "correlationId":"123" - |} - |""".stripMargin) - - result.left.value mustBe InternalServerError(error) - - } - } - - private def createRimValidationResponse = - RimValidationErrorResponse( - emcsCorrelationId = "correlationId", - message = Seq("Validation error(s) occurred"), - validatorResults = Seq( - createRimError(8080L, "/con:Control[1]/con:Parameter[1]/urn:IE815[1]/urn:DateOfDispatch[1]"), - createRimError( - 8090L, - "/con:Control[1]/con:Parameter[1]/urn:IE818[1]/urn:AcceptedOrRejectedReportOfReceiptExport[1]/urn:Attributes[1][1]", - None - ) - ) - ) - - private def expectedRimValidationResponse = - EisErrorResponsePresentation( - localDateTime, - "Validation error", - "Validation error(s) occurred", - "correlationId", - Some( - Seq( - createLocalValidationError(8080L, "/urn:IE815[1]/urn:DateOfDispatch[1]"), - createLocalValidationError( - 8090L, - "/urn:IE818[1]/urn:AcceptedOrRejectedReportOfReceiptExport[1]/urn:Attributes[1][1]", - None - ) - ) - ) - ) - - private def createRimError( - errorCode: BigInt, - location: String, - origValue: Option[String] = Some(localDateTime.toString) - ): RimValidatorResults = - RimValidatorResults( - errorCategory = Some("business"), - errorType = Some(errorCode), - errorReason = Some("The Date of Dispatch you entered is incorrect"), - errorLocation = Some(location), - originalAttributeValue = origValue - ) - - private def createLocalValidationError( - errorCode: BigInt, - location: String, - origValue: Option[String] = Some(localDateTime.toString) - ): ValidationResponse = - ValidationResponse( - errorCategory = Some("business"), - errorType = Some(errorCode), - errorReason = Some("The Date of Dispatch you entered is incorrect"), - errorLocation = Some(location), - originalAttributeValue = origValue - ) -} diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReaderSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReaderSpec.scala deleted file mode 100644 index 72f2d7cf3..000000000 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderETDSHttpReaderSpec.scala +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2023 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import org.mockito.MockitoSugar.when -import org.scalatest.EitherValues -import org.scalatest.Inspectors.forAll -import org.scalatestplus.mockito.MockitoSugar.mock -import org.scalatestplus.play.PlaySpec -import play.api.http.Status.{BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND, SERVICE_UNAVAILABLE} -import play.api.libs.json.Json -import play.api.mvc.Results.Status -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EisErrorResponsePresentation -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.TestUtils.{getExciseTraderValidationETDSResponse, getPreValidateTraderETDSMessageResponseAllFail} -import uk.gov.hmrc.http.HttpResponse - -import java.time.Instant - -class PreValidateTraderETDSHttpReaderSpec extends PlaySpec with EitherValues { - - private val validResponse = getExciseTraderValidationETDSResponse - private val businessError = getPreValidateTraderETDSMessageResponseAllFail - - private val now = Instant.now - private val dateTimeService = mock[DateTimeService] - - when(dateTimeService.timestamp()).thenReturn(now) - - private val preValidateTraderHttpReader = - PreValidateTraderETDSHttpReader("123", "GB123", "date time", dateTimeService) - - "read" should { - "return PreValidateTraderEISResponse when success" in { - - val result = preValidateTraderHttpReader.read( - "ANY", - "/foo", - HttpResponse(200, Json.toJson(validResponse).toString()) - ) - - val responseObject = result.toOption.value - responseObject.processingDateTime mustBe validResponse.processingDateTime - responseObject.validationResult mustBe validResponse.validationResult - - } - - "return PreValidateTraderErrorResponse when error occurs" in { - - val result = preValidateTraderHttpReader.read( - "ANY", - "/foo", - HttpResponse(200, Json.toJson(businessError).toString()) - ) - - val responseObject = result.toOption.value - - responseObject.processingDateTime mustBe businessError.processingDateTime - responseObject.validationResult mustBe businessError.validationResult - } - - forAll(Seq(BAD_REQUEST, NOT_FOUND)) { statusCode => - s"return $statusCode" when { - s"$statusCode has returned from HttpResponse" in { - - val expectedResponse = Status(statusCode)( - Json.toJson( - EisErrorResponsePresentation( - now, - "PreValidateTrader error", - "Error occurred during PreValidateTrader request", - "123" - ) - ) - ) - - val result = preValidateTraderHttpReader - .read( - "ANY", - "/foo", - HttpResponse(statusCode, "") - ) - .left - .value - - result mustBe expectedResponse - } - } - } - - forAll(Seq(INTERNAL_SERVER_ERROR, SERVICE_UNAVAILABLE)) { statusCode => - s"return $statusCode" when { - s"$statusCode has returned from HttpResponse" in { - - val expectedResponse = Status(statusCode)( - Json.toJson( - EisErrorResponsePresentation( - now, - "Internal Server Error", - "Unexpected error occurred while processing PreValidateTrader request", - "123" - ) - ) - ) - - val result = preValidateTraderHttpReader - .read( - "ANY", - "/foo", - HttpResponse(statusCode, "") - ) - .left - .value - - result mustBe expectedResponse - } - } - } - - } -} diff --git a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReaderSpec.scala b/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReaderSpec.scala deleted file mode 100644 index 1dfe9a04e..000000000 --- a/test/uk/gov/hmrc/excisemovementcontrolsystemapi/connectors/util/PreValidateTraderHttpReaderSpec.scala +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package uk.gov.hmrc.excisemovementcontrolsystemapi.connectors.util - -import org.mockito.MockitoSugar.when -import org.scalatest.EitherValues -import org.scalatest.Inspectors.forAll -import org.scalatestplus.mockito.MockitoSugar.mock -import org.scalatestplus.play.PlaySpec -import play.api.http.Status.{BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND, SERVICE_UNAVAILABLE} -import play.api.libs.json.Json -import play.api.mvc.Results.Status -import uk.gov.hmrc.excisemovementcontrolsystemapi.models.EisErrorResponsePresentation -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.DateTimeService -import uk.gov.hmrc.excisemovementcontrolsystemapi.utils.TestUtils.{getPreValidateTraderErrorEISResponse, getPreValidateTraderSuccessEISResponse} -import uk.gov.hmrc.http.HttpResponse - -import java.time.Instant - -class PreValidateTraderHttpReaderSpec extends PlaySpec with EitherValues { - - private val validResponse = getPreValidateTraderSuccessEISResponse - private val businessError = getPreValidateTraderErrorEISResponse - - private val now = Instant.now - private val dateTimeService = mock[DateTimeService] - - when(dateTimeService.timestamp()).thenReturn(now) - - private val preValidateTraderHttpReader = PreValidateTraderHttpReader("123", "GB123", "date time", dateTimeService) - - "read" should { - "return PreValidateTraderEISResponse when success" in { - - val result = preValidateTraderHttpReader.read( - "ANY", - "/foo", - HttpResponse(200, Json.toJson(validResponse).toString()) - ) - - val responseObject = result.toOption.value.exciseTraderValidationResponse - responseObject.validationTimestamp mustBe validResponse.exciseTraderValidationResponse.validationTimestamp - responseObject.exciseTraderResponse(0) mustBe validResponse.exciseTraderValidationResponse.exciseTraderResponse(0) - - } - - "return PreValidateTraderErrorResponse when error occurs" in { - - val result = preValidateTraderHttpReader.read( - "ANY", - "/foo", - HttpResponse(200, Json.toJson(businessError).toString()) - ) - - val responseObject = result.toOption.value - - responseObject.exciseTraderValidationResponse.validationTimestamp mustBe businessError.exciseTraderValidationResponse.validationTimestamp - responseObject.exciseTraderValidationResponse.exciseTraderResponse( - 0 - ) mustBe businessError.exciseTraderValidationResponse.exciseTraderResponse(0) - } - - forAll(Seq(BAD_REQUEST, NOT_FOUND, INTERNAL_SERVER_ERROR, SERVICE_UNAVAILABLE)) { statusCode => - s"return $statusCode" when { - s"$statusCode has returned from HttpResponse" in { - - val expectedResponse = Status(statusCode)( - Json.toJson( - EisErrorResponsePresentation( - now, - "PreValidateTrader error", - "Error occurred during PreValidateTrader request", - "123" - ) - ) - ) - - val result = preValidateTraderHttpReader - .read( - "ANY", - "/foo", - HttpResponse(statusCode, "") - ) - .left - .value - - result mustBe expectedResponse - } - } - } - - } -}